From c99bed75f991f7b0439f803f818560fcded5282a Mon Sep 17 00:00:00 2001 From: Greg Clark Date: Thu, 16 Jul 2026 19:48:37 -0400 Subject: [PATCH 1/3] feat(switchyard-llm-client): add HTTP LLM client crate speaking the neutral IR Signed-off-by: Greg Clark --- Cargo.lock | 2334 ------------------------ Cargo.toml | 1 + crates/libsy-llm-client/Cargo.toml | 27 + crates/libsy-llm-client/README.md | 174 ++ crates/libsy-llm-client/src/backend.rs | 246 +++ crates/libsy-llm-client/src/client.rs | 815 +++++++++ crates/libsy-llm-client/src/error.rs | 157 ++ crates/libsy-llm-client/src/lib.rs | 30 + crates/libsy-llm-client/src/raw.rs | 22 + 9 files changed, 1472 insertions(+), 2334 deletions(-) delete mode 100644 Cargo.lock create mode 100644 crates/libsy-llm-client/Cargo.toml create mode 100644 crates/libsy-llm-client/README.md create mode 100644 crates/libsy-llm-client/src/backend.rs create mode 100644 crates/libsy-llm-client/src/client.rs create mode 100644 crates/libsy-llm-client/src/error.rs create mode 100644 crates/libsy-llm-client/src/lib.rs create mode 100644 crates/libsy-llm-client/src/raw.rs diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index b432a6d9..00000000 --- a/Cargo.lock +++ /dev/null @@ -1,2334 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - -[[package]] -name = "anstream" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" - -[[package]] -name = "anstyle-parse" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys 0.61.2", -] - -[[package]] -name = "arc-swap" -version = "1.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b" -dependencies = [ - "rustversion", -] - -[[package]] -name = "async-stream" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" -dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "async-trait" -version = "0.1.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "autocfg" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" - -[[package]] -name = "aws-lc-rs" -version = "1.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4342d8937fc7e5dd9b1c60292261c0670c882a2cd1719cfc11b1af41731e32ad" -dependencies = [ - "aws-lc-sys", - "zeroize", -] - -[[package]] -name = "aws-lc-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d9ceb1da931507a12f4fccea479dccd00da1943e1b4ae72d8e502d707361444" -dependencies = [ - "cc", - "cmake", - "dunce", - "fs_extra", - "pkg-config", -] - -[[package]] -name = "axum" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" -dependencies = [ - "axum-core", - "bytes", - "form_urlencoded", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-util", - "itoa", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "serde_core", - "serde_json", - "serde_path_to_error", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "axum-core" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "http-body-util", - "mime", - "pin-project-lite", - "sync_wrapper", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "axum-server" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1df331683d982a0b9492b38127151e6453639cd34926eb9c07d4cd8c6d22bfc" -dependencies = [ - "arc-swap", - "bytes", - "either", - "fs-err", - "http", - "http-body", - "hyper", - "hyper-util", - "pin-project-lite", - "rustls", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower-service", -] - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "bitflags" -version = "2.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" - -[[package]] -name = "bumpalo" -version = "3.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" - -[[package]] -name = "bytes" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" - -[[package]] -name = "cc" -version = "1.2.67" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17dd265a7d0f31ef544e1b20e03add05d3b45b491b633b10d67145d2acc1a38" -dependencies = [ - "find-msvc-tools", - "jobserver", - "libc", - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "cfg_aliases" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" - -[[package]] -name = "chacha20" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" -dependencies = [ - "cfg-if", - "cpufeatures", - "rand_core 0.10.1", -] - -[[package]] -name = "clap" -version = "4.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd059f9da4f5c36b3787f65d38ccaab1cc315f07b01f89abc8359ee6a8205011" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "clap_lex" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" - -[[package]] -name = "cmake" -version = "0.1.58" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" -dependencies = [ - "cc", -] - -[[package]] -name = "colorchoice" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" - -[[package]] -name = "core-foundation" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "cpufeatures" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" -dependencies = [ - "libc", -] - -[[package]] -name = "diff" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" - -[[package]] -name = "displaydoc" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - -[[package]] -name = "either" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" - -[[package]] -name = "equivalent" -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 = "find-msvc-tools" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "fs-err" -version = "3.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91aa448ca50d7e79433bdf3ee8d99215430d2ec02ade5aefab2a073a1822e8a" -dependencies = [ - "autocfg", - "tokio", -] - -[[package]] -name = "fs_extra" -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-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" - -[[package]] -name = "futures-executor" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" - -[[package]] -name = "futures-macro" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" - -[[package]] -name = "futures-task" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" - -[[package]] -name = "futures-util" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "slab", -] - -[[package]] -name = "getrandom" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "r-efi", - "rand_core 0.10.1", - "wasm-bindgen", -] - -[[package]] -name = "h2" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash", -] - -[[package]] -name = "hashbrown" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "http" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" -dependencies = [ - "bytes", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hyper" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" -dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" -dependencies = [ - "http", - "hyper", - "hyper-util", - "rustls", - "rustls-native-certs", - "tokio", - "tokio-rustls", - "tower-service", -] - -[[package]] -name = "hyper-util" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" -dependencies = [ - "base64", - "bytes", - "futures-channel", - "futures-util", - "http", - "http-body", - "hyper", - "ipnet", - "libc", - "percent-encoding", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", -] - -[[package]] -name = "icu_collections" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" -dependencies = [ - "displaydoc", - "potential_utf", - "utf8_iter", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" - -[[package]] -name = "icu_properties" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" -dependencies = [ - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" - -[[package]] -name = "icu_provider" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" -dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "indexmap" -version = "2.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" -dependencies = [ - "equivalent", - "hashbrown 0.17.1", -] - -[[package]] -name = "ipnet" -version = "2.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" - -[[package]] -name = "itoa" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" - -[[package]] -name = "jobserver" -version = "0.1.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" -dependencies = [ - "getrandom 0.4.3", - "libc", -] - -[[package]] -name = "js-sys" -version = "0.3.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" -dependencies = [ - "cfg-if", - "futures-util", - "wasm-bindgen", -] - -[[package]] -name = "libc" -version = "0.2.186" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" - -[[package]] -name = "libsy-examples" -version = "0.1.0" -dependencies = [ - "async-trait", - "futures", - "switchyard-libsy", - "switchyard-protocol", - "tokio", - "tokio-stream", -] - -[[package]] -name = "libyaml-rs" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e126dda6f34391ab7b444f9922055facc83c07a910da3eb16f1e4d9c45dc777" - -[[package]] -name = "litemap" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" - -[[package]] -name = "lru" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" -dependencies = [ - "hashbrown 0.15.5", -] - -[[package]] -name = "lru-slab" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" - -[[package]] -name = "matchit" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" - -[[package]] -name = "memchr" -version = "2.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "mio" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.61.2", -] - -[[package]] -name = "once_cell" -version = "1.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" - -[[package]] -name = "once_cell_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" - -[[package]] -name = "openssl-probe" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link", -] - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "pin-project-lite" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" - -[[package]] -name = "pkg-config" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" - -[[package]] -name = "portable-atomic" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" - -[[package]] -name = "potential_utf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" -dependencies = [ - "zerovec", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "pretty_assertions" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" -dependencies = [ - "diff", - "yansi", -] - -[[package]] -name = "proc-macro2" -version = "1.0.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "pyo3" -version = "0.28.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91fd8e38a3b50ed1167fb981cd6fd60147e091784c427b8f7183a7ee32c31c12" -dependencies = [ - "libc", - "once_cell", - "portable-atomic", - "pyo3-build-config", - "pyo3-ffi", - "pyo3-macros", -] - -[[package]] -name = "pyo3-async-runtimes" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e7364a95bf00e8377bbf9b0f09d7ff9715a29d8fcf93b47d1a967363b973178" -dependencies = [ - "futures-channel", - "futures-util", - "once_cell", - "pin-project-lite", - "pyo3", - "tokio", -] - -[[package]] -name = "pyo3-build-config" -version = "0.28.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e368e7ddfdeb98c9bca7f8383be1648fd84ab466bf2bc015e94008db6d35611e" -dependencies = [ - "target-lexicon", -] - -[[package]] -name = "pyo3-ffi" -version = "0.28.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f29e10af80b1f7ccaf7f69eace800a03ecd13e883acfacc1e5d0988605f651e" -dependencies = [ - "libc", - "pyo3-build-config", -] - -[[package]] -name = "pyo3-macros" -version = "0.28.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df6e520eff47c45997d2fc7dd8214b25dd1310918bbb2642156ef66a67f29813" -dependencies = [ - "proc-macro2", - "pyo3-macros-backend", - "quote", - "syn", -] - -[[package]] -name = "pyo3-macros-backend" -version = "0.28.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cdc218d835738f81c2338f822078af45b4afdf8b2e33cbb5916f108b813acb" -dependencies = [ - "heck", - "proc-macro2", - "pyo3-build-config", - "quote", - "syn", -] - -[[package]] -name = "pythonize" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b79f670c9626c8b651c0581011b57b6ba6970bb69faf01a7c4c0cfc81c43f95" -dependencies = [ - "pyo3", - "serde", -] - -[[package]] -name = "quinn" -version = "0.11.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" -dependencies = [ - "bytes", - "cfg_aliases", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls", - "socket2", - "thiserror", - "tokio", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-proto" -version = "0.11.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560" -dependencies = [ - "bytes", - "getrandom 0.4.3", - "lru-slab", - "rand 0.10.2", - "rand_pcg", - "ring", - "rustc-hash", - "rustls", - "rustls-pki-types", - "slab", - "thiserror", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-udp" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2", - "tracing", - "windows-sys 0.61.2", -] - -[[package]] -name = "quote" -version = "1.0.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" - -[[package]] -name = "rand" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" -dependencies = [ - "libc", - "rand_chacha", - "rand_core 0.6.4", -] - -[[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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.17", -] - -[[package]] -name = "rand_core" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" - -[[package]] -name = "rand_pcg" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" -dependencies = [ - "rand_core 0.10.1", -] - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags", -] - -[[package]] -name = "reqwest" -version = "0.12.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" -dependencies = [ - "base64", - "bytes", - "futures-core", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "js-sys", - "log", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls", - "rustls-native-certs", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-rustls", - "tokio-util", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", -] - -[[package]] -name = "ring" -version = "0.17.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.17", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustc-hash" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" - -[[package]] -name = "rustls" -version = "0.23.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138" -dependencies = [ - "aws-lc-rs", - "log", - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-native-certs" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" -dependencies = [ - "openssl-probe", - "rustls-pki-types", - "schannel", - "security-framework", -] - -[[package]] -name = "rustls-pki-types" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046" -dependencies = [ - "web-time", - "zeroize", -] - -[[package]] -name = "rustls-webpki" -version = "0.103.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" -dependencies = [ - "aws-lc-rs", - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" - -[[package]] -name = "ryu" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" - -[[package]] -name = "schannel" -version = "0.1.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "security-framework" -version = "3.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.150" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "serde_path_to_error" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" -dependencies = [ - "itoa", - "serde", - "serde_core", -] - -[[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "shlex" -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 = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" - -[[package]] -name = "smallvec" -version = "1.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" - -[[package]] -name = "socket2" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "switchyard-components" -version = "0.1.0" -dependencies = [ - "async-stream", - "async-trait", - "futures-util", - "parking_lot", - "rand 0.8.7", - "reqwest", - "serde", - "serde_json", - "switchyard-core", - "switchyard-translation", - "tokio", - "tracing", -] - -[[package]] -name = "switchyard-components-v2" -version = "0.1.0" -dependencies = [ - "async-stream", - "async-trait", - "futures-core", - "futures-util", - "parking_lot", - "rand 0.8.7", - "reqwest", - "serde", - "serde_json", - "switchyard-components", - "switchyard-components-v2-macros", - "switchyard-core", - "tokio", - "toml", - "tracing", - "yaml_serde", -] - -[[package]] -name = "switchyard-components-v2-macros" -version = "0.1.0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "switchyard-core" -version = "0.1.0" -dependencies = [ - "async-trait", - "futures-core", - "lru", - "rand 0.8.7", - "serde", - "serde_json", - "thiserror", - "tokio", - "tracing", -] - -[[package]] -name = "switchyard-libsy" -version = "0.1.0" -dependencies = [ - "async-trait", - "futures", - "rand 0.8.7", - "serde_json", - "switchyard-protocol", - "tokio", - "tokio-stream", -] - -[[package]] -name = "switchyard-protocol" -version = "0.1.0" -dependencies = [ - "async-trait", - "futures", - "serde", - "serde_json", -] - -[[package]] -name = "switchyard-py" -version = "0.1.0" -dependencies = [ - "async-trait", - "futures-util", - "pyo3", - "pyo3-async-runtimes", - "pythonize", - "serde", - "serde_json", - "switchyard-components", - "switchyard-components-v2", - "switchyard-core", - "switchyard-libsy", - "switchyard-server", - "switchyard-translation", - "tokio", - "tracing", -] - -[[package]] -name = "switchyard-server" -version = "0.1.0" -dependencies = [ - "async-stream", - "async-trait", - "axum", - "axum-server", - "clap", - "futures-util", - "http-body-util", - "rustls", - "serde", - "serde_json", - "switchyard-components-v2", - "switchyard-core", - "switchyard-translation", - "tokio", - "tower", - "tracing", -] - -[[package]] -name = "switchyard-skill-distillation" -version = "0.1.0" -dependencies = [ - "async-trait", - "serde", - "serde_json", - "thiserror", - "tokio", -] - -[[package]] -name = "switchyard-translation" -version = "0.1.0" -dependencies = [ - "async-stream", - "futures", - "pretty_assertions", - "serde", - "serde_json", - "switchyard-protocol", - "thiserror", -] - -[[package]] -name = "syn" -version = "2.0.119" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "target-lexicon" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" - -[[package]] -name = "thiserror" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tinystr" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tinyvec" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "tokio" -version = "1.52.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317fafbbe3f02fc663dad00ea6186197de963cd4190e86a26d8d0fae095539af" -dependencies = [ - "bytes", - "libc", - "mio", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys 0.61.2", -] - -[[package]] -name = "tokio-macros" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-stream" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "toml" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.22.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" -dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "toml_write", - "winnow", -] - -[[package]] -name = "toml_write" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" - -[[package]] -name = "tower" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-http" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" -dependencies = [ - "bitflags", - "bytes", - "futures-util", - "http", - "http-body", - "pin-project-lite", - "tower", - "tower-layer", - "tower-service", - "url", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" -dependencies = [ - "log", - "pin-project-lite", - "tracing-core", -] - -[[package]] -name = "tracing-core" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" -dependencies = [ - "once_cell", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "unicode-ident" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasm-bindgen" -version = "0.2.126" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.76" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.126" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.126" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.126" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wasm-streams" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "web-sys" -version = "0.3.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "winnow" -version = "0.7.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" -dependencies = [ - "memchr", -] - -[[package]] -name = "writeable" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" - -[[package]] -name = "yaml_serde" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08c7c1b1a6a7c8a6b2741a6c21a4f8918e51899b111cfa08d1288202656e3975" -dependencies = [ - "indexmap", - "itoa", - "libyaml-rs", - "ryu", - "serde", -] - -[[package]] -name = "yansi" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" - -[[package]] -name = "yoke" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" -dependencies = [ - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zerocopy" -version = "0.8.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zerofrom" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" - -[[package]] -name = "zerotrie" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zmij" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/Cargo.toml b/Cargo.toml index ec2ee2c7..551d52cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ resolver = "2" members = [ "crates/libsy", "crates/libsy-examples", + "crates/libsy-llm-client", "crates/switchyard-components", "crates/switchyard-components-v2", "crates/switchyard-components-v2-macros", diff --git a/crates/libsy-llm-client/Cargo.toml b/crates/libsy-llm-client/Cargo.toml new file mode 100644 index 00000000..b930adcb --- /dev/null +++ b/crates/libsy-llm-client/Cargo.toml @@ -0,0 +1,27 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +[package] +name = "switchyard-llm-client" +version = "0.1.0" +description = "HTTP LLM client speaking Switchyard's neutral IR directly" +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true + +[dependencies] +switchyard-protocol = { path = "../libsy-protocol", version = "0.1.0" } +switchyard-translation = { path = "../switchyard-translation", version = "0.1.0" } +reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls-native-roots", "stream"] } +async-trait = "0.1" +futures = "0.3" +futures-util = "0.3" +serde_json = "1" +thiserror = "2" + +[dev-dependencies] +pretty_assertions = "1" +tokio = { version = "1", features = ["macros", "rt-multi-thread"] } +wiremock = "0.6" diff --git a/crates/libsy-llm-client/README.md b/crates/libsy-llm-client/README.md new file mode 100644 index 00000000..5bc9afee --- /dev/null +++ b/crates/libsy-llm-client/README.md @@ -0,0 +1,174 @@ + + +# libsy-llm-client + +An HTTP client that speaks Switchyard's neutral IR directly. You hand it a +[`libsy_protocol::Request`] and a model name; it looks up the configured backend, +encodes the request to that backend's wire format, adds auth and forwards your +headers, makes the call with a shared `reqwest::Client`, and decodes the reply +back into a [`libsy_protocol::Response`] — buffered or streamed. + +It depends only on `libsy-protocol` and `switchyard-translation`; no server, no +provider SDK. For an HTTP front door built on top of it, see [`libsy-proxy`]. + +## Concepts + +- **Two-layer map.** A client is built from `model → wire format → Backend`, so + one model can be served over several upstream formats. `call(request, model, format)` + picks the backend by `(model, format)`. +- **Backends.** A [`Backend`] is one of `OpenAiChat`, `OpenAiResponses`, or + `Anthropic`, each wrapping an [`HttpBackendConfig`] (`base_url`, `api_key`, + static `extra_headers`). The variant fixes the URL path and auth scheme + (Bearer vs `x-api-key` + `anthropic-version`). +- **Model rewrite.** The resolved model name is both the map key and the model id + sent upstream — it overwrites whatever `model` the request arrived with. +- **Streaming is chosen by the request.** If the encoded body has `stream: true` + (i.e. `request.llm_request.stream`), you get `LlmResponse::Stream`; otherwise + `LlmResponse::Agg`. + +## Add the dependency + +Within this workspace: + +```toml +[dependencies] +switchyard-llm-client = { path = "../libsy-llm-client" } +switchyard-protocol = { path = "../libsy-protocol" } +switchyard-translation = { path = "../switchyard-translation" } # for WireFormat +``` + +## Quickstart + +### Build a client + +```rust +use std::collections::{BTreeMap, HashMap}; +use switchyard_llm_client::{Backend, HttpBackendConfig, LlmModelClient}; +use switchyard_translation::WireFormat; + +fn build_client() -> libsy_llm_client::Result { + let openai = HttpBackendConfig { + base_url: "https://api.openai.com/v1".to_string(), + api_key: std::env::var("OPENAI_API_KEY").ok(), + extra_headers: BTreeMap::new(), + }; + + // model → format → backend + let map = HashMap::from([( + "gpt-4o-mini".to_string(), + HashMap::from([(WireFormat::OpenAiChat, Backend::OpenAiChat(openai))]), + )]); + + LlmModelClient::new(map) +} +``` + +### Buffered call + +```rust +use libsy_protocol::{completion_text, text_request, Request}; +use switchyard_translation::WireFormat; + +async fn ask(client: &LlmModelClient) -> libsy_llm_client::Result { + let request = Request { + llm_request: text_request(None, "Say hello in five words."), + raw_request: None, + metadata: None, + }; + + // model_name wins over request.llm_request.model; it is also sent upstream. + let response = client + .call(request, Some("gpt-4o-mini"), WireFormat::OpenAiChat) + .await?; + + // Buffered backends return `Agg`; a stream can be folded with `.aggregate()`. + let agg = response.llm_response.aggregate().await.unwrap(); + Ok(completion_text(&agg)) +} +``` + +### Streaming call + +Set `stream` on the IR request and drive the returned chunk stream: + +```rust +use futures_util::StreamExt; +use libsy_protocol::{text_request, LlmResponse, LlmResponseChunk, Request}; +use switchyard_translation::WireFormat; + +async fn stream(client: &LlmModelClient) -> libsy_llm_client::Result<()> { + let mut llm_request = text_request(None, "Count to five."); + llm_request.stream = true; + let request = Request { llm_request, raw_request: None, metadata: None }; + + let response = client + .call(request, Some("gpt-4o-mini"), WireFormat::OpenAiChat) + .await?; + + if let LlmResponse::Stream(mut chunks) = response.llm_response { + while let Some(item) = chunks.next().await { + match item { + Ok(LlmResponseChunk::TextDelta { text, .. }) => print!("{text}"), + Ok(_) => {} // usage, tool-call deltas, message start/stop + Err(error) => return Err(libsy_llm_client::LlmClientError::Stream(error.to_string())), + } + } + } + Ok(()) +} +``` + +## Cross-format translation + +The request/response are translated through the neutral IR, so the inbound shape +you build and the backend's wire format are independent. Pointing a +`WireFormat::AnthropicMessages` backend at an Anthropic endpoint while building +requests with the same helpers works the same way — pass the matching format to +`call`. Register several formats under one model to serve it over more than one +upstream API: + +```rust +let backends = HashMap::from([ + (WireFormat::OpenAiChat, Backend::OpenAiChat(openai_chat_cfg)), + (WireFormat::OpenAiResponses, Backend::OpenAiResponses(openai_resp_cfg)), + (WireFormat::AnthropicMessages, Backend::Anthropic(anthropic_cfg)), +]); +let map = HashMap::from([("my-model".to_string(), backends)]); +// client.formats_for("my-model") -> the configured formats +``` + +## Headers & auth + +- The `Backend` variant sets auth: OpenAI formats send `Authorization: Bearer `; + Anthropic sends `x-api-key: ` plus `anthropic-version`. +- `request.metadata.http_headers` are forwarded upstream, **except** reserved ones: + `host`, `content-length`, `connection`, and the backend-owned + `authorization` / `x-api-key` / `anthropic-version` / `content-type`. So a + caller's placeholder credential never overrides the backend's real key. +- Per-backend static headers go in `HttpBackendConfig::extra_headers`. + +## Errors + +`call` returns [`LlmClientError`]: + +| Variant | When | +|---------|------| +| `MissingModel` | no `model_name` arg and no `request.llm_request.model` | +| `UnknownModel(model)` | model has no backends configured | +| `UnknownModelFormat { model, format }` | model has no backend for that format | +| `Translation(msg)` | encode/decode failed in the translation engine | +| `Transport(msg)` | connect/timeout/transport failure | +| `ContextWindowExceeded { model, message }` | upstream 400 detected as a context overflow (checked before `UpstreamHttp`, so callers can evict-and-retry) | +| `UpstreamHttp { status, body }` | any other non-2xx upstream response | +| `Stream(msg)` | mid-stream read / malformed frame | + +[`libsy_protocol::Request`]: ../libsy-protocol +[`libsy_protocol::Response`]: ../libsy-protocol +[`libsy-proxy`]: ../libsy-proxy +[`Backend`]: src/backend.rs +[`HttpBackendConfig`]: src/backend.rs +[`LlmModelClient`]: src/client.rs +[`LlmClientError`]: src/error.rs diff --git a/crates/libsy-llm-client/src/backend.rs b/crates/libsy-llm-client/src/backend.rs new file mode 100644 index 00000000..0512f2ac --- /dev/null +++ b/crates/libsy-llm-client/src/backend.rs @@ -0,0 +1,246 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Per-provider backend configuration: wire format, upstream URL, and auth. + +use std::collections::BTreeMap; + +use reqwest::RequestBuilder; +use switchyard_protocol::WireFormat; + +use crate::error::is_overflow_body; + +const ANTHROPIC_VERSION: &str = "2023-06-01"; + +// Canonical OpenAI phrase plus NVIDIA/LiteLLM wrap variants. Adding a new +// provider-wrap is a one-line entry here, not a fork of the parsing logic. +const OPENAI_OVERFLOW_PHRASES: &[&str] = &[ + "maximum context length", + "context length exceeded", + "context window", + "context length is only", + "please reduce the length of the input", +]; + +// Anthropic has no structured `error.code`, so detection is phrase-based only. +const ANTHROPIC_OVERFLOW_PHRASES: &[&str] = &[ + "prompt is too long", + "maximum number of tokens", + "context window", + "context length", +]; + +/// Shared HTTP configuration for one upstream backend. +#[derive(Clone, Debug)] +pub struct HttpBackendConfig { + /// Base URL of the provider API (e.g. `https://api.openai.com/v1`). + pub base_url: String, + /// API key for the provider, loaded by the caller. `None` sends no auth. + pub api_key: Option, + /// Static headers added to every outbound call to this backend. + pub extra_headers: BTreeMap, +} + +/// A configured upstream backend, one variant per built-in wire format. +/// +/// The variant fixes the wire format, URL path, and auth scheme together so no +/// invalid combination can be constructed. +#[derive(Clone, Debug)] +pub enum Backend { + /// OpenAI-compatible Chat Completions API. + OpenAiChat(HttpBackendConfig), + /// OpenAI Responses API. + OpenAiResponses(HttpBackendConfig), + /// Anthropic Messages API. + Anthropic(HttpBackendConfig), +} + +impl Backend { + /// The wire format the request IR is encoded to for this backend. + pub fn wire_format(&self) -> WireFormat { + match self { + Backend::OpenAiChat(_) => WireFormat::OpenAiChat, + Backend::OpenAiResponses(_) => WireFormat::OpenAiResponses, + Backend::Anthropic(_) => WireFormat::AnthropicMessages, + } + } + + // Shared HTTP config, regardless of variant. + fn config(&self) -> &HttpBackendConfig { + match self { + Backend::OpenAiChat(config) + | Backend::OpenAiResponses(config) + | Backend::Anthropic(config) => config, + } + } + + /// The fully resolved upstream URL for this backend's endpoint. + /// + /// Tolerates base URLs that already include the provider path (or a bare + /// `/v1`), matching the join rules of the existing native backends. + pub fn url(&self) -> String { + let base_url = self.config().base_url.trim_end_matches('/'); + match self { + Backend::OpenAiChat(_) => openai_url(base_url, "/chat/completions"), + Backend::OpenAiResponses(_) => openai_url(base_url, "/responses"), + Backend::Anthropic(_) => anthropic_url(base_url), + } + } + + /// Applies this backend's auth and version headers to a request builder. + /// + /// OpenAI variants use `Authorization: Bearer `; Anthropic uses + /// `x-api-key: ` plus the required `anthropic-version` header. + pub fn apply_auth(&self, mut builder: RequestBuilder) -> RequestBuilder { + let api_key = self.config().api_key.as_deref(); + match self { + Backend::OpenAiChat(_) | Backend::OpenAiResponses(_) => { + if let Some(api_key) = api_key { + builder = builder.bearer_auth(api_key); + } + } + Backend::Anthropic(_) => { + builder = builder.header("anthropic-version", ANTHROPIC_VERSION); + if let Some(api_key) = api_key { + builder = builder.header("x-api-key", api_key); + } + } + } + builder + } + + /// Static per-backend headers to forward on every call. + pub fn extra_headers(&self) -> &BTreeMap { + &self.config().extra_headers + } + + /// Whether an upstream 400 `body` looks like a context-window overflow for + /// this backend's provider. + pub(crate) fn is_context_overflow(&self, body: &str) -> bool { + match self { + Backend::OpenAiChat(_) | Backend::OpenAiResponses(_) => is_overflow_body( + body, + |value| { + value + .get("error") + .and_then(|err| err.get("code")) + .and_then(serde_json::Value::as_str) + == Some("context_length_exceeded") + }, + OPENAI_OVERFLOW_PHRASES, + ), + Backend::Anthropic(_) => is_overflow_body(body, |_| false, ANTHROPIC_OVERFLOW_PHRASES), + } + } +} + +// Accept either a root `/v1` URL or an already-specific OpenAI endpoint URL. +fn openai_url(base_url: &str, suffix: &str) -> String { + let base_root = base_url + .strip_suffix("/chat/completions") + .or_else(|| base_url.strip_suffix("/responses")) + .unwrap_or(base_url); + format!("{base_root}{suffix}") +} + +// Accept a bare host, a `/v1` root, or an already-specific `/v1/messages` URL. +fn anthropic_url(base_url: &str) -> String { + if base_url.ends_with("/v1/messages") { + base_url.to_string() + } else if base_url.ends_with("/v1") { + format!("{base_url}/messages") + } else { + format!("{base_url}/v1/messages") + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn config(base_url: &str) -> HttpBackendConfig { + HttpBackendConfig { + base_url: base_url.to_string(), + api_key: Some("secret".to_string()), + extra_headers: BTreeMap::new(), + } + } + + #[test] + fn openai_chat_url_joins_bare_v1() { + let backend = Backend::OpenAiChat(config("https://api.openai.com/v1")); + assert_eq!(backend.url(), "https://api.openai.com/v1/chat/completions"); + } + + #[test] + fn openai_chat_url_tolerates_trailing_slash_and_existing_suffix() { + assert_eq!( + Backend::OpenAiChat(config("https://api.openai.com/v1/")).url(), + "https://api.openai.com/v1/chat/completions" + ); + assert_eq!( + Backend::OpenAiChat(config("https://api.openai.com/v1/chat/completions")).url(), + "https://api.openai.com/v1/chat/completions" + ); + } + + #[test] + fn openai_responses_url_uses_responses_path() { + assert_eq!( + Backend::OpenAiResponses(config("https://api.openai.com/v1")).url(), + "https://api.openai.com/v1/responses" + ); + } + + #[test] + fn anthropic_url_join_cases() { + assert_eq!( + Backend::Anthropic(config("https://api.anthropic.com")).url(), + "https://api.anthropic.com/v1/messages" + ); + assert_eq!( + Backend::Anthropic(config("https://api.anthropic.com/v1")).url(), + "https://api.anthropic.com/v1/messages" + ); + assert_eq!( + Backend::Anthropic(config("https://api.anthropic.com/v1/messages")).url(), + "https://api.anthropic.com/v1/messages" + ); + } + + #[test] + fn wire_format_matches_variant() { + assert_eq!( + Backend::OpenAiChat(config("x")).wire_format(), + WireFormat::OpenAiChat + ); + assert_eq!( + Backend::OpenAiResponses(config("x")).wire_format(), + WireFormat::OpenAiResponses + ); + assert_eq!( + Backend::Anthropic(config("x")).wire_format(), + WireFormat::AnthropicMessages + ); + } + + #[test] + fn openai_detects_canonical_and_wrapped_overflow() { + let backend = Backend::OpenAiChat(config("x")); + assert!(backend + .is_context_overflow(r#"{"error":{"code":"context_length_exceeded","message":"x"}}"#)); + // NVIDIA/LiteLLM message wrap with no structured code. + assert!(backend.is_context_overflow( + r#"{"error":{"message":"the model's context length is only 131072 tokens"}}"# + )); + assert!(!backend.is_context_overflow(r#"{"error":{"code":"invalid_api_key"}}"#)); + } + + #[test] + fn anthropic_detects_prompt_too_long() { + let backend = Backend::Anthropic(config("x")); + assert!(backend + .is_context_overflow(r#"{"error":{"message":"prompt is too long: 200000 tokens"}}"#)); + assert!(!backend.is_context_overflow(r#"{"error":{"message":"overloaded"}}"#)); + } +} diff --git a/crates/libsy-llm-client/src/client.rs b/crates/libsy-llm-client/src/client.rs new file mode 100644 index 00000000..f3bfc3e4 --- /dev/null +++ b/crates/libsy-llm-client/src/client.rs @@ -0,0 +1,815 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! [`TranslatingLlmClient`] — the crate's single public entry point: encode a neutral +//! request, call the configured backend over HTTP, decode the neutral response. + +use std::collections::{BTreeMap, HashMap}; + +use async_trait::async_trait; +use futures_util::StreamExt; +use reqwest::RequestBuilder; +use serde_json::Value; +use switchyard_protocol::{ + Context, Decision, LlmResponse, Metadata, Request, Response, RoutedLlmClient, +}; +use switchyard_translation::{ + decode_aggregated_response, decode_request, decode_stream, encode_aggregated_response, + encode_request, encode_stream, WireFormat, +}; + +use crate::backend::Backend; +use crate::error::{LlmClientError, Result}; +use crate::raw::RawResponse; + +// Headers this client owns or that are hop-by-hop; never forwarded from the +// caller's metadata. Auth/version/content-type are set by the backend or the +// JSON body, so a forwarded copy would either be ignored or conflict. Compared +// case-insensitively. Aligns with `_SENSITIVE_HEADERS` in the Python +// `switchyard/lib/request_metadata.py` forwarding logic. +const RESERVED_HEADERS: &[&str] = &[ + "host", + "content-length", + "connection", + "authorization", + "x-api-key", + "anthropic-version", + "content-type", +]; + +/// How one model is served: the `default_backend` used when the request does not +/// pin a wire format, plus any `other_backends` reachable over additional formats. +#[derive(Clone, Debug)] +pub struct ModelConfig { + model_name: String, + default_backend: Backend, + other_backends: Option>, +} + +impl ModelConfig { + /// A model named `model_name` served by `default_backend`, optionally reachable + /// over additional wire formats via `other_backends`. + pub fn new( + model_name: impl Into, + default_backend: Backend, + other_backends: Option>, + ) -> Self { + Self { + model_name: model_name.into(), + default_backend, + other_backends, + } + } +} + +/// A client that dispatches neutral-IR requests to per-model HTTP backends. +/// +/// Construct it with a list of [`ModelConfig`]s — one per model, each naming a +/// default [`Backend`] and any additional per-format backends. Each call resolves +/// the model and wire format, encodes the request to that backend's wire format, +/// applies auth and forwarded headers, sends the HTTP request with a shared +/// [`reqwest::Client`], and decodes the response back to the neutral IR (buffered +/// or streamed). +pub struct TranslatingLlmClient { + model_to_config: HashMap, + client: reqwest::Client, +} + +impl TranslatingLlmClient { + /// Builds a client over the given [`ModelConfig`]s, with a fresh shared HTTP + /// client and the built-in translation codecs. + pub fn new(model_configs: &[ModelConfig]) -> Result { + let client = reqwest::Client::builder().build().map_err(|error| { + LlmClientError::Transport(format!("failed to build HTTP client: {error}")) + })?; + let model_to_config = model_configs + .iter() + .map(|config| (config.model_name.clone(), config.clone())) + .collect(); + + Ok(Self { + model_to_config, + client, + }) + } + + /// The backend serving `model` over `format` — the default backend when its + /// format matches, otherwise a matching entry in `other_backends`; `None` when + /// the model is unknown or has no backend for `format`. + pub fn backend_for(&self, model: &str, format: WireFormat) -> Option<&Backend> { + self.model_to_config.get(model).and_then(|config| { + if config.default_backend.wire_format() == format { + Some(&config.default_backend) + } else { + config + .other_backends + .as_ref() + .and_then(|backends| backends.iter().find(|b| b.wire_format() == format)) + } + }) + } + + /// Calls the backend for `model_name` (or the request's own model), over the + /// wire format the request pins in its metadata (else the model's default + /// backend), and returns the neutral response. + /// + /// Resolution: `model_name` wins over `request.llm_request.model`; the + /// resolved name is both the outer map key and the model id written into the + /// request before translation. Errors with [`LlmClientError::MissingModel`] + /// when neither is set, [`LlmClientError::UnknownModel`] when the model has + /// no backends, and [`LlmClientError::UnknownModelFormat`] when the model has + /// no backend for `format`. + pub async fn call_rewrite_model( + &self, + _ctx: Context, + request: Request, + model_name: Option<&str>, + ) -> Result { + // Own the request's parts so the model can be set without a `mut` param + // and without cloning the messages. `raw_request` is unused here. + let Request { + mut llm_request, + metadata, + .. + } = request; + + let model = model_name + .map(str::to_string) + .or_else(|| llm_request.model.clone()) + .ok_or(LlmClientError::MissingModel)?; + + let orig_format = metadata.as_ref().and_then(|m| m.wire_format); + let wire_format = orig_format.unwrap_or( + self.model_to_config + .get(&model) + .map(|config| config.default_backend.wire_format()) + .ok_or(LlmClientError::UnknownModel(model.clone()))?, + ); + let backend = + self.backend_for(&model, wire_format) + .ok_or(LlmClientError::UnknownModelFormat { + model: model.clone(), + format: wire_format, + })?; + + // The resolved name is the upstream model id (per the crate contract). + llm_request.model = Some(model.clone()); + + let mut body = encode_request(&llm_request, wire_format) + .map_err(|error| LlmClientError::Translation(error.to_string()))?; + // `encode_request` round-trips a preserved same-format body verbatim, + // which keeps the caller's original `model`; force the resolved model so + // the upstream always sees the target id. + set_json_model(&mut body, &model); + let streaming = body.get("stream").and_then(Value::as_bool).unwrap_or(false); + + let builder = self.client.post(backend.url()).json(&body); + let builder = forward_metadata_headers(builder, metadata.as_ref()); + let builder = apply_extra_headers(builder, backend); + let builder = backend.apply_auth(builder); + + let http_response = builder + .send() + .await + .map_err(|error| LlmClientError::Transport(error.to_string()))?; + let status = http_response.status(); + if !status.is_success() { + let body = http_response + .text() + .await + .unwrap_or_else(|error| format!("")); + if status == reqwest::StatusCode::BAD_REQUEST && backend.is_context_overflow(&body) { + return Err(LlmClientError::ContextWindowExceeded { + model, + message: body, + }); + } + return Err(LlmClientError::UpstreamHttp { + status: status.as_u16(), + body, + }); + } + + let llm_response = if streaming { + // Adapt the reqwest body stream to plain bytes; the SSE-decode itself is + // transport-agnostic and lives in `switchyard-translation`. + let bytes = http_response.bytes_stream().map(|chunk| { + chunk.map(|bytes| bytes.to_vec()).map_err(|error| { + Box::new(LlmClientError::Stream(format!( + "stream read failed: {error}" + ))) as Box + }) + }); + let chunks = decode_stream(bytes, wire_format) + .map_err(|error| LlmClientError::Stream(error.to_string()))?; + LlmResponse::Stream(chunks) + } else { + let body = http_response.json::().await.map_err(|error| { + LlmClientError::Transport(format!("invalid upstream JSON: {error}")) + })?; + let agg = decode_aggregated_response(&body, wire_format) + .map_err(|error| LlmClientError::Translation(error.to_string()))?; + LlmResponse::Agg(agg) + }; + + Ok(Response { + llm_response, + metadata, + }) + } + + /// The whole decode → call → encode path a wire endpoint needs, in one call. + /// + /// Decodes `raw_http_request` from `wire_format` to the neutral IR, serves it via + /// [`call_rewrite_model`](Self::call_rewrite_model) — the *upstream* wire format is + /// resolved there from the model's backend, independently of `wire_format` — then + /// encodes the neutral response back into `wire_format`. The result is a buffered + /// [`RawResponse::Buffered`] JSON body or a streamed [`RawResponse::Stream`] of + /// wire events (the caller frames the stream as SSE). The response's `model` is + /// restamped with the model the request asked for, never the upstream id. + /// + /// `http_headers` are carried through as the request's + /// [`Metadata::http_headers`] and forwarded to the upstream (minus the reserved + /// set); pass `None` to forward nothing. + pub async fn call_rewrite_model_raw( + &self, + ctx: Context, + raw_http_request: Value, + http_headers: Option>, + model: Option<&str>, + wire_format: WireFormat, + ) -> Result { + let llm_request = decode_request(wire_format, &raw_http_request) + .map_err(|error| LlmClientError::Translation(error.to_string()))?; + // The model the client asked for; restamped onto the response so it never + // leaks the upstream id. + let requested_model = llm_request.model.clone(); + + let request = Request { + llm_request, + raw_request: None, + metadata: Some(Metadata { + session_id: None, + agent_id: None, + task_id: None, + correlation_id: None, + extra_metadata: None, + http_headers, + wire_format: None, + }), + }; + let response = self.call_rewrite_model(ctx, request, model).await?; + + match response.llm_response { + LlmResponse::Agg(agg) => { + let body = + encode_aggregated_response(&agg, wire_format, requested_model.as_deref()) + .map_err(|error| LlmClientError::Translation(error.to_string()))?; + Ok(RawResponse::Buffered(body)) + } + LlmResponse::Stream(chunks) => { + let events = encode_stream(chunks, wire_format, requested_model) + .map_err(|error| LlmClientError::Stream(error.to_string()))?; + Ok(RawResponse::Stream(events)) + } + } + } +} + +#[async_trait] +impl RoutedLlmClient for TranslatingLlmClient { + async fn call( + &self, + ctx: Context, + request: Request, + decision: std::sync::Arc, + ) -> std::result::Result> { + let model_name = Some(decision.selected_model()); + self.call_rewrite_model(ctx, request, model_name) + .await + .map_err(|error| Box::new(error) as Box) + } +} + +// Forwards caller-supplied metadata headers, skipping the reserved set. +fn forward_metadata_headers( + mut builder: RequestBuilder, + metadata: Option<&Metadata>, +) -> RequestBuilder { + let Some(headers) = metadata.and_then(|metadata| metadata.http_headers.as_ref()) else { + return builder; + }; + for (name, value) in headers { + if is_reserved_header(name) { + continue; + } + builder = builder.header(name, value); + } + builder +} + +// Adds the backend's static per-call headers. +fn apply_extra_headers(mut builder: RequestBuilder, backend: &Backend) -> RequestBuilder { + for (name, value) in backend.extra_headers() { + builder = builder.header(name, value); + } + builder +} + +// Overwrites the outbound body's `model` field with the resolved model id. +fn set_json_model(body: &mut Value, model: &str) { + if let Value::Object(object) = body { + object.insert("model".to_string(), Value::String(model.to_string())); + } +} + +// Case-insensitive membership test against RESERVED_HEADERS. +fn is_reserved_header(name: &str) -> bool { + RESERVED_HEADERS + .iter() + .any(|reserved| name.eq_ignore_ascii_case(reserved)) +} + +#[cfg(test)] +mod tests { + use std::collections::BTreeMap; + + use serde_json::json; + use switchyard_protocol::{completion_text, text_request, LlmRequest}; + use wiremock::matchers::{method, path}; + use wiremock::{Mock, MockServer, ResponseTemplate}; + + use super::*; + use crate::backend::HttpBackendConfig; + + fn config(base_url: &str) -> HttpBackendConfig { + HttpBackendConfig { + base_url: base_url.to_string(), + api_key: Some("secret".to_string()), + extra_headers: BTreeMap::new(), + } + } + + // A one-model config list: "gpt" served over OpenAI Chat at base_url. + fn chat_map(base_url: &str) -> Vec { + vec![ModelConfig::new( + "gpt", + Backend::OpenAiChat(config(base_url)), + None, + )] + } + + fn request_for(model: Option<&str>, stream: bool) -> Request { + let mut llm_request = text_request(model.map(str::to_string), "hi"); + llm_request.stream = stream; + Request { + llm_request, + raw_request: None, + metadata: None, + } + } + + // A request that pins `format` in its metadata, so the client resolves that + // wire format instead of the model's default backend. + fn request_with_wire_format(model: &str, format: WireFormat) -> Request { + let mut request = request_for(Some(model), false); + request.metadata = Some(Metadata { + session_id: None, + agent_id: None, + task_id: None, + correlation_id: None, + extra_metadata: None, + http_headers: None, + wire_format: Some(format), + }); + request + } + + #[test] + fn reserved_headers_are_case_insensitive() { + assert!(is_reserved_header("Authorization")); + assert!(is_reserved_header("content-type")); + assert!(is_reserved_header("X-Api-Key")); + assert!(!is_reserved_header("x-request-id")); + } + + #[tokio::test] + async fn missing_model_errors() { + let client = TranslatingLlmClient::new(&[]).unwrap(); + let Err(error) = client + .call_rewrite_model(Context::default(), request_for(None, false), None) + .await + else { + panic!("expected an error"); + }; + assert!(matches!(error, LlmClientError::MissingModel)); + } + + #[tokio::test] + async fn unknown_model_errors() { + let client = TranslatingLlmClient::new(&[]).unwrap(); + let Err(error) = client + .call_rewrite_model(Context::default(), request_for(Some("gpt"), false), None) + .await + else { + panic!("expected an error"); + }; + assert!(matches!(error, LlmClientError::UnknownModel(model) if model == "gpt")); + } + + #[tokio::test] + async fn unknown_model_format_errors() { + // "gpt" exists but only over OpenAI Chat; the request pins Anthropic. + let client = TranslatingLlmClient::new(&chat_map("https://example.test/v1")).unwrap(); + let Err(error) = client + .call_rewrite_model( + Context::default(), + request_with_wire_format("gpt", WireFormat::AnthropicMessages), + None, + ) + .await + else { + panic!("expected an error"); + }; + assert!(matches!( + error, + LlmClientError::UnknownModelFormat { model, format } + if model == "gpt" && format == WireFormat::AnthropicMessages + )); + } + + #[test] + fn backend_for_resolves_configured_format() { + let client = TranslatingLlmClient::new(&chat_map("https://example.test/v1")).unwrap(); + // "gpt" is served over OpenAI Chat only; other formats and models miss. + assert!(client.backend_for("gpt", WireFormat::OpenAiChat).is_some()); + assert!(client + .backend_for("gpt", WireFormat::AnthropicMessages) + .is_none()); + assert!(client + .backend_for("missing", WireFormat::OpenAiChat) + .is_none()); + } + + #[tokio::test] + async fn model_name_arg_wins_over_request_model() { + let client = TranslatingLlmClient::new(&[]).unwrap(); + // Arg "b" is looked up (and reported), not the request's "a". + let Err(error) = client + .call_rewrite_model(Context::default(), request_for(Some("a"), false), Some("b")) + .await + else { + panic!("expected an error"); + }; + assert!(matches!(error, LlmClientError::UnknownModel(model) if model == "b")); + } + + #[tokio::test] + async fn buffered_openai_chat_round_trips() { + let server = MockServer::start().await; + Mock::given(method("POST")) + .and(path("/v1/chat/completions")) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "id": "chatcmpl-1", + "model": "gpt", + "choices": [{ + "index": 0, + "message": {"role": "assistant", "content": "Hi there"}, + "finish_reason": "stop" + }], + "usage": {"prompt_tokens": 1, "completion_tokens": 2, "total_tokens": 3} + }))) + .mount(&server) + .await; + + let client = TranslatingLlmClient::new(&chat_map(&format!("{}/v1", server.uri()))).unwrap(); + + let response = client + .call_rewrite_model(Context::default(), request_for(Some("gpt"), false), None) + .await + .unwrap(); + let agg = response + .llm_response + .into_agg() + .await + .expect("buffered response"); + assert_eq!(completion_text(&agg), "Hi there"); + } + + #[tokio::test] + async fn rewrites_model_to_resolved_upstream_id() { + // Inbound body says "switchyard"; the upstream must receive "gpt". + let server = MockServer::start().await; + Mock::given(method("POST")) + .and(path("/v1/chat/completions")) + .and(wiremock::matchers::body_partial_json(json!({"model": "gpt"}))) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "id": "1", "model": "gpt", + "choices": [{"index": 0, "message": {"role": "assistant", "content": "ok"}, "finish_reason": "stop"}], + "usage": {} + }))) + .mount(&server) + .await; + + let client = TranslatingLlmClient::new(&chat_map(&format!("{}/v1", server.uri()))).unwrap(); + // Inbound model differs from the map key / resolved model. + client + .call_rewrite_model( + Context::default(), + request_for(Some("switchyard"), false), + Some("gpt"), + ) + .await + .unwrap(); + // The body_partial_json matcher asserts the upstream saw model "gpt". + } + + #[tokio::test] + async fn streaming_openai_chat_aggregates() { + let server = MockServer::start().await; + let sse = "data: {\"choices\":[{\"delta\":{\"content\":\"Hello\"}}]}\n\n\ + data: {\"choices\":[{\"delta\":{\"content\":\" world\"}}]}\n\n\ + data: [DONE]\n\n"; + Mock::given(method("POST")) + .and(path("/v1/chat/completions")) + .respond_with(ResponseTemplate::new(200).set_body_raw(sse, "text/event-stream")) + .mount(&server) + .await; + + let client = TranslatingLlmClient::new(&chat_map(&format!("{}/v1", server.uri()))).unwrap(); + + let response = client + .call_rewrite_model(Context::default(), request_for(Some("gpt"), true), None) + .await + .unwrap(); + assert!(matches!(response.llm_response, LlmResponse::Stream(_))); + let agg = response.llm_response.into_agg().await.unwrap(); + assert_eq!(completion_text(&agg), "Hello world"); + } + + #[tokio::test] + async fn upstream_500_is_upstream_http() { + let server = MockServer::start().await; + Mock::given(method("POST")) + .respond_with(ResponseTemplate::new(500).set_body_string("boom")) + .mount(&server) + .await; + + let client = TranslatingLlmClient::new(&chat_map(&format!("{}/v1", server.uri()))).unwrap(); + + let Err(error) = client + .call_rewrite_model(Context::default(), request_for(Some("gpt"), false), None) + .await + else { + panic!("expected an error"); + }; + assert!(matches!( + error, + LlmClientError::UpstreamHttp { status: 500, .. } + )); + } + + #[tokio::test] + async fn context_overflow_400_is_mapped() { + let server = MockServer::start().await; + Mock::given(method("POST")) + .respond_with(ResponseTemplate::new(400).set_body_json(json!({ + "error": {"code": "context_length_exceeded", "message": "too big"} + }))) + .mount(&server) + .await; + + let client = TranslatingLlmClient::new(&chat_map(&format!("{}/v1", server.uri()))).unwrap(); + + let Err(error) = client + .call_rewrite_model(Context::default(), request_for(Some("gpt"), false), None) + .await + else { + panic!("expected an error"); + }; + assert!(matches!( + error, + LlmClientError::ContextWindowExceeded { model, .. } if model == "gpt" + )); + } + + #[tokio::test] + async fn forwards_metadata_headers_except_reserved() { + let server = MockServer::start().await; + Mock::given(method("POST")) + .and(wiremock::matchers::header("x-request-id", "abc")) + // A forwarded Authorization must NOT override the backend's bearer key. + .and(wiremock::matchers::header("authorization", "Bearer secret")) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "id": "1", "model": "gpt", + "choices": [{"index": 0, "message": {"role": "assistant", "content": "ok"}, "finish_reason": "stop"}], + "usage": {} + }))) + .mount(&server) + .await; + + let mut headers = BTreeMap::new(); + headers.insert("x-request-id".to_string(), "abc".to_string()); + headers.insert("authorization".to_string(), "Bearer client-key".to_string()); + let request = Request { + llm_request: LlmRequest { + model: Some("gpt".to_string()), + ..LlmRequest::default() + }, + raw_request: None, + metadata: Some(Metadata { + session_id: None, + agent_id: None, + task_id: None, + correlation_id: None, + extra_metadata: None, + http_headers: Some(headers), + wire_format: None, + }), + }; + + let client = TranslatingLlmClient::new(&chat_map(&format!("{}/v1", server.uri()))).unwrap(); + + // Matchers assert forwarded x-request-id survives and reserved + // authorization is the backend's, not the client's. + client + .call_rewrite_model(Context::default(), request, None) + .await + .unwrap(); + } + + // Minimal `Decision` for driving the client through the `RoutedLlmClient` trait. + struct FixedDecision(&'static str); + + impl Decision for FixedDecision { + fn selected_model(&self) -> &str { + self.0 + } + fn reasoning(&self) -> Option<&str> { + None + } + fn as_any(&self) -> &dyn std::any::Any { + self + } + } + + // Exercises the `RoutedLlmClient` impl: `call` resolves the upstream model from the + // decision (the request carries none) and round-trips a buffered response. + #[tokio::test] + async fn routed_llm_client_serves_the_decision_model() { + let server = MockServer::start().await; + Mock::given(method("POST")) + .and(path("/v1/chat/completions")) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "id": "chatcmpl-1", + "model": "gpt", + "choices": [{ + "index": 0, + "message": {"role": "assistant", "content": "routed hi"}, + "finish_reason": "stop" + }], + "usage": {"prompt_tokens": 1, "completion_tokens": 2, "total_tokens": 3} + }))) + .mount(&server) + .await; + + let client = TranslatingLlmClient::new(&chat_map(&format!("{}/v1", server.uri()))).unwrap(); + let decision: std::sync::Arc = std::sync::Arc::new(FixedDecision("gpt")); + // Called through the trait; the request has no model, so "gpt" comes from the decision. + let response = client + .call(Context::default(), request_for(None, false), decision) + .await + .unwrap(); + let agg = response + .llm_response + .into_agg() + .await + .expect("buffered response"); + assert_eq!(completion_text(&agg), "routed hi"); + } + + // Raw path, buffered: decode an OpenAI Chat body -> call -> encode back to OpenAI + // Chat JSON, with the client-facing `model` restamped over the upstream id. + #[tokio::test] + async fn call_rewrite_model_raw_round_trips_buffered_json() { + let server = MockServer::start().await; + Mock::given(method("POST")) + .and(path("/v1/chat/completions")) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "id": "chatcmpl-1", + "model": "gpt", + "choices": [{ + "index": 0, + "message": {"role": "assistant", "content": "Hi there"}, + "finish_reason": "stop" + }], + "usage": {"prompt_tokens": 1, "completion_tokens": 2, "total_tokens": 3} + }))) + .mount(&server) + .await; + + let client = TranslatingLlmClient::new(&chat_map(&format!("{}/v1", server.uri()))).unwrap(); + let raw = json!({ + "model": "client-facing", + "messages": [{"role": "user", "content": "hi"}] + }); + let RawResponse::Buffered(body) = client + .call_rewrite_model_raw( + Context::default(), + raw, + None, + Some("gpt"), + WireFormat::OpenAiChat, + ) + .await + .unwrap() + else { + panic!("expected a buffered response"); + }; + + assert_eq!(body["choices"][0]["message"]["content"], "Hi there"); + // The client sees the model it asked for, not the upstream "gpt". + assert_eq!(body["model"], "client-facing"); + } + + // Raw path, streaming: an inbound `stream: true` request yields an unframed stream + // of OpenAI Chat chunk objects whose deltas reassemble the completion. + #[tokio::test] + async fn call_rewrite_model_raw_streams_wire_events() { + use futures::StreamExt; + + let server = MockServer::start().await; + let sse = "data: {\"choices\":[{\"delta\":{\"content\":\"Hello\"}}]}\n\n\ + data: {\"choices\":[{\"delta\":{\"content\":\" world\"}}]}\n\n\ + data: [DONE]\n\n"; + Mock::given(method("POST")) + .and(path("/v1/chat/completions")) + .respond_with(ResponseTemplate::new(200).set_body_raw(sse, "text/event-stream")) + .mount(&server) + .await; + + let client = TranslatingLlmClient::new(&chat_map(&format!("{}/v1", server.uri()))).unwrap(); + let raw = json!({ + "model": "client-facing", + "messages": [{"role": "user", "content": "hi"}], + "stream": true + }); + let RawResponse::Stream(stream) = client + .call_rewrite_model_raw( + Context::default(), + raw, + None, + Some("gpt"), + WireFormat::OpenAiChat, + ) + .await + .unwrap() + else { + panic!("expected a streamed response"); + }; + + let events: Vec = stream.map(|item| item.unwrap()).collect().await; + assert!(!events.is_empty(), "expected at least one wire event"); + let content: String = events + .iter() + .filter_map(|event| event["choices"][0]["delta"]["content"].as_str()) + .collect(); + assert_eq!(content, "Hello world"); + } + + // Raw path forwards caller headers (minus the reserved set) to the upstream. + #[tokio::test] + async fn call_rewrite_model_raw_forwards_headers() { + let server = MockServer::start().await; + Mock::given(method("POST")) + .and(wiremock::matchers::header("x-request-id", "abc")) + // A forwarded authorization must NOT override the backend's bearer key. + .and(wiremock::matchers::header("authorization", "Bearer secret")) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "id": "1", "model": "gpt", + "choices": [{"index": 0, "message": {"role": "assistant", "content": "ok"}, "finish_reason": "stop"}], + "usage": {} + }))) + .mount(&server) + .await; + + let mut headers = BTreeMap::new(); + headers.insert("x-request-id".to_string(), "abc".to_string()); + headers.insert("authorization".to_string(), "Bearer client-key".to_string()); + + let client = TranslatingLlmClient::new(&chat_map(&format!("{}/v1", server.uri()))).unwrap(); + let raw = json!({"model": "gpt", "messages": [{"role": "user", "content": "hi"}]}); + // Matchers assert the forwarded x-request-id survives and reserved + // authorization is the backend's, not the client's. + client + .call_rewrite_model_raw( + Context::default(), + raw, + Some(headers), + Some("gpt"), + WireFormat::OpenAiChat, + ) + .await + .unwrap(); + } +} diff --git a/crates/libsy-llm-client/src/error.rs b/crates/libsy-llm-client/src/error.rs new file mode 100644 index 00000000..d02c0523 --- /dev/null +++ b/crates/libsy-llm-client/src/error.rs @@ -0,0 +1,157 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Error type for the LLM client, plus shared context-window-overflow detection. +//! +//! The overflow detection is ported from +//! `switchyard-components/src/backends/context_overflow.rs`; that helper is +//! crate-private there and this crate cannot depend on `switchyard-components`, +//! so the small, self-contained logic is vendored here. + +use serde_json::Value; +use switchyard_translation::WireFormat; +use thiserror::Error; + +/// Result alias for LLM client operations. +pub type Result = std::result::Result; + +/// Failures surfaced while resolving, translating, sending, or decoding a call. +#[derive(Debug, Error)] +pub enum LlmClientError { + /// The resolved model name has no entry in the client's backend map. + #[error("no backend configured for model {0:?}")] + UnknownModel(String), + + /// The model exists but has no backend configured for the requested format. + #[error("model {model:?} has no backend for format {format}")] + UnknownModelFormat { + /// Model that was resolved. + model: String, + /// Wire format requested for the call. + format: WireFormat, + }, + + /// Neither an explicit `model_name` nor a model on the request was given. + #[error("no model given: pass model_name or set request.llm_request.model")] + MissingModel, + + /// Request encoding or response decoding failed in the translation engine. + #[error("translation failed: {0}")] + Translation(String), + + /// The HTTP request could not be sent (connect/timeout/transport failure). + #[error("upstream transport error: {0}")] + Transport(String), + + /// An upstream 400 whose body is detected as a context-window overflow. + /// + /// Kept distinct from [`LlmClientError::UpstreamHttp`] and checked first so a + /// caller can implement evict-and-retry by matching this variant instead of + /// sniffing error strings. + #[error("context window exceeded for model {model}: {message}")] + ContextWindowExceeded { + /// Model that overflowed, as sent upstream. + model: String, + /// Raw upstream error body. + message: String, + }, + + /// A non-2xx upstream response that is not a context-window overflow. + #[error("upstream returned HTTP {status}: {body}")] + UpstreamHttp { + /// Upstream HTTP status code. + status: u16, + /// Raw upstream error body. + body: String, + }, + + /// A streamed response failed mid-flight (read error or malformed frame). + #[error("stream error: {0}")] + Stream(String), +} + +/// Detects a context-overflow body using a provider-supplied structured check +/// and a substring phrase list. +/// +/// Parses the body once, runs the structured check (e.g. against `error.code`), +/// then falls back to matching phrases against `error.message` or — when the +/// body is not JSON — the raw body. Centralizing the shape means each new +/// provider-wrap of the canonical error is a one-line phrase entry, not a fork +/// of the parsing logic. +pub(crate) fn is_overflow_body(body: &str, structured_check: F, phrases: &[&str]) -> bool +where + F: Fn(&Value) -> bool, +{ + if let Ok(value) = serde_json::from_str::(body) { + if structured_check(&value) { + return true; + } + if let Some(message) = value + .get("error") + .and_then(|err| err.get("message")) + .and_then(Value::as_str) + { + if contains_any(message, phrases) { + return true; + } + } + } + // Some upstream proxies return plain-text bodies; fall through to a string + // match on the raw body. + contains_any(body, phrases) +} + +// Case-insensitive substring match of any phrase against the message. +fn contains_any(message: &str, phrases: &[&str]) -> bool { + let lower = message.to_ascii_lowercase(); + phrases.iter().any(|phrase| lower.contains(phrase)) +} + +#[cfg(test)] +mod tests { + use super::*; + + const PHRASES: &[&str] = &["context window", "too long"]; + + fn never(_value: &Value) -> bool { + false + } + + #[test] + fn structured_check_short_circuits() { + let body = r#"{"error":{"code":"context_length_exceeded","message":"unrelated"}}"#; + let matched = is_overflow_body( + body, + |value| { + value + .get("error") + .and_then(|err| err.get("code")) + .and_then(Value::as_str) + == Some("context_length_exceeded") + }, + &[], + ); + assert!(matched); + } + + #[test] + fn falls_back_to_message_phrase_match() { + let body = r#"{"error":{"message":"prompt too long"}}"#; + assert!(is_overflow_body(body, never, PHRASES)); + } + + #[test] + fn matches_plain_text_body() { + assert!(is_overflow_body( + "plain text mentioning context window", + never, + PHRASES + )); + } + + #[test] + fn non_match_returns_false() { + let body = r#"{"error":{"message":"rate limit exceeded"}}"#; + assert!(!is_overflow_body(body, never, PHRASES)); + } +} diff --git a/crates/libsy-llm-client/src/lib.rs b/crates/libsy-llm-client/src/lib.rs new file mode 100644 index 00000000..97fe5b3a --- /dev/null +++ b/crates/libsy-llm-client/src/lib.rs @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! HTTP LLM client that speaks Switchyard's neutral IR directly. +//! +//! [`TranslatingLlmClient`] maps a model name (and the wire format resolved from +//! the request) to a [`Backend`], +//! encodes a [`switchyard_protocol::Request`] to that backend's wire format via +//! `switchyard-translation`, applies auth and forwards caller headers, makes the +//! HTTP call with a shared [`reqwest::Client`], and decodes the wire response +//! back to a [`switchyard_protocol::Response`] — supporting both buffered and +//! streamed responses. +//! +//! The crate depends only on `libsy-protocol` and `switchyard-translation`. Neutral +//! IR encode/decode — including SSE stream decoding — lives in +//! `switchyard-translation`; this crate is the HTTP transport around it. The +//! context-overflow detection in [`mod@error`] is vendored from +//! `switchyard-components` (whose copy is crate-private and unavailable here); a +//! future refactor could promote it to a shared location. + +pub mod backend; +pub mod client; +pub mod error; +pub mod raw; + +pub use backend::{Backend, HttpBackendConfig}; +pub use client::{ModelConfig, TranslatingLlmClient}; +pub use error::{LlmClientError, Result}; +pub use raw::RawResponse; +pub use switchyard_translation::RawEventStream; diff --git a/crates/libsy-llm-client/src/raw.rs b/crates/libsy-llm-client/src/raw.rs new file mode 100644 index 00000000..7391c1a0 --- /dev/null +++ b/crates/libsy-llm-client/src/raw.rs @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! The raw wire result of a call. +//! +//! The encoders that produce it — buffered JSON and the streamed wire-event +//! encoder — live in `switchyard-translation` alongside the rest of the neutral-IR +//! codecs; this module only holds the result type they feed. + +use serde_json::Value; +use switchyard_translation::RawEventStream; + +/// The wire result of +/// [`call_rewrite_model_raw`](crate::TranslatingLlmClient::call_rewrite_model_raw): +/// a buffered JSON body, or a live stream of wire events — both already in the +/// requested wire format. +pub enum RawResponse { + /// A complete JSON response body. + Buffered(Value), + /// A live stream of wire-format event objects, ready for SSE framing. + Stream(RawEventStream), +} From 13de2b6f61c855c00f0d30efef03164c06e1df09 Mon Sep 17 00:00:00 2001 From: Graham King Date: Mon, 20 Jul 2026 17:06:20 -0400 Subject: [PATCH 2/3] Tweaks from feedback - Update README to match new API - Make tests return Error instead of `unwrap` to match project rules. - Redact API key from Debug output Thanks Code Rabbit Assisted-by: Codex:GPT 5.6 Sol medium Signed-off-by: Graham King --- crates/libsy-llm-client/README.md | 103 ++++++++++------- crates/libsy-llm-client/src/backend.rs | 14 ++- crates/libsy-llm-client/src/client.rs | 147 ++++++++++++++----------- crates/libsy-llm-client/src/lib.rs | 7 -- 4 files changed, 153 insertions(+), 118 deletions(-) diff --git a/crates/libsy-llm-client/README.md b/crates/libsy-llm-client/README.md index 5bc9afee..b4d31a9c 100644 --- a/crates/libsy-llm-client/README.md +++ b/crates/libsy-llm-client/README.md @@ -6,19 +6,20 @@ SPDX-License-Identifier: Apache-2.0 # libsy-llm-client An HTTP client that speaks Switchyard's neutral IR directly. You hand it a -[`libsy_protocol::Request`] and a model name; it looks up the configured backend, +[`switchyard_protocol::Request`] and a model name; it looks up the configured backend, encodes the request to that backend's wire format, adds auth and forwards your headers, makes the call with a shared `reqwest::Client`, and decodes the reply -back into a [`libsy_protocol::Response`] — buffered or streamed. +back into a [`switchyard_protocol::Response`] — buffered or streamed. -It depends only on `libsy-protocol` and `switchyard-translation`; no server, no +It depends only on `switchyard-protocol` and `switchyard-translation`; no server, no provider SDK. For an HTTP front door built on top of it, see [`libsy-proxy`]. ## Concepts -- **Two-layer map.** A client is built from `model → wire format → Backend`, so - one model can be served over several upstream formats. `call(request, model, format)` - picks the backend by `(model, format)`. +- **Model configs.** A client is built from [`ModelConfig`] values. Each model has a + default [`Backend`] and can have additional backends for other wire formats. + [`TranslatingLlmClient::call_rewrite_model`] uses the request's metadata wire format + when set, otherwise the model's default backend. - **Backends.** A [`Backend`] is one of `OpenAiChat`, `OpenAiResponses`, or `Anthropic`, each wrapping an [`HttpBackendConfig`] (`base_url`, `api_key`, static `extra_headers`). The variant fixes the URL path and auth scheme @@ -45,34 +46,35 @@ switchyard-translation = { path = "../switchyard-translation" } # for WireForm ### Build a client ```rust -use std::collections::{BTreeMap, HashMap}; -use switchyard_llm_client::{Backend, HttpBackendConfig, LlmModelClient}; -use switchyard_translation::WireFormat; +use std::collections::BTreeMap; +use switchyard_llm_client::{ + Backend, HttpBackendConfig, ModelConfig, TranslatingLlmClient, +}; -fn build_client() -> libsy_llm_client::Result { +fn build_client() -> switchyard_llm_client::Result { let openai = HttpBackendConfig { base_url: "https://api.openai.com/v1".to_string(), api_key: std::env::var("OPENAI_API_KEY").ok(), extra_headers: BTreeMap::new(), }; - // model → format → backend - let map = HashMap::from([( - "gpt-4o-mini".to_string(), - HashMap::from([(WireFormat::OpenAiChat, Backend::OpenAiChat(openai))]), - )]); + let models = [ModelConfig::new( + "gpt-4o-mini", + Backend::OpenAiChat(openai), + None, + )]; - LlmModelClient::new(map) + TranslatingLlmClient::new(&models) } ``` ### Buffered call ```rust -use libsy_protocol::{completion_text, text_request, Request}; -use switchyard_translation::WireFormat; +use switchyard_llm_client::{LlmClientError, TranslatingLlmClient}; +use switchyard_protocol::{completion_text, text_request, Context, LlmResponse, Request}; -async fn ask(client: &LlmModelClient) -> libsy_llm_client::Result { +async fn ask(client: &TranslatingLlmClient) -> switchyard_llm_client::Result { let request = Request { llm_request: text_request(None, "Say hello in five words."), raw_request: None, @@ -81,12 +83,15 @@ async fn ask(client: &LlmModelClient) -> libsy_llm_client::Result { // model_name wins over request.llm_request.model; it is also sent upstream. let response = client - .call(request, Some("gpt-4o-mini"), WireFormat::OpenAiChat) + .call_rewrite_model(Context::default(), request, Some("gpt-4o-mini")) .await?; - // Buffered backends return `Agg`; a stream can be folded with `.aggregate()`. - let agg = response.llm_response.aggregate().await.unwrap(); - Ok(completion_text(&agg)) + match response.llm_response { + LlmResponse::Agg(agg) => Ok(completion_text(&agg)), + LlmResponse::Stream(_) => Err(LlmClientError::Stream( + "expected a buffered response".to_string(), + )), + } } ``` @@ -96,16 +101,16 @@ Set `stream` on the IR request and drive the returned chunk stream: ```rust use futures_util::StreamExt; -use libsy_protocol::{text_request, LlmResponse, LlmResponseChunk, Request}; -use switchyard_translation::WireFormat; +use switchyard_llm_client::{LlmClientError, TranslatingLlmClient}; +use switchyard_protocol::{text_request, Context, LlmResponse, LlmResponseChunk, Request}; -async fn stream(client: &LlmModelClient) -> libsy_llm_client::Result<()> { +async fn stream(client: &TranslatingLlmClient) -> switchyard_llm_client::Result<()> { let mut llm_request = text_request(None, "Count to five."); llm_request.stream = true; let request = Request { llm_request, raw_request: None, metadata: None }; let response = client - .call(request, Some("gpt-4o-mini"), WireFormat::OpenAiChat) + .call_rewrite_model(Context::default(), request, Some("gpt-4o-mini")) .await?; if let LlmResponse::Stream(mut chunks) = response.llm_response { @@ -113,7 +118,7 @@ async fn stream(client: &LlmModelClient) -> libsy_llm_client::Result<()> { match item { Ok(LlmResponseChunk::TextDelta { text, .. }) => print!("{text}"), Ok(_) => {} // usage, tool-call deltas, message start/stop - Err(error) => return Err(libsy_llm_client::LlmClientError::Stream(error.to_string())), + Err(error) => return Err(LlmClientError::Stream(error.to_string())), } } } @@ -126,18 +131,31 @@ async fn stream(client: &LlmModelClient) -> libsy_llm_client::Result<()> { The request/response are translated through the neutral IR, so the inbound shape you build and the backend's wire format are independent. Pointing a `WireFormat::AnthropicMessages` backend at an Anthropic endpoint while building -requests with the same helpers works the same way — pass the matching format to -`call`. Register several formats under one model to serve it over more than one -upstream API: +requests with the same helpers works the same way. Set `request.metadata.wire_format` +to select a non-default backend before calling `call_rewrite_model`. Register several +formats under one model to serve it over more than one upstream API: ```rust -let backends = HashMap::from([ - (WireFormat::OpenAiChat, Backend::OpenAiChat(openai_chat_cfg)), - (WireFormat::OpenAiResponses, Backend::OpenAiResponses(openai_resp_cfg)), - (WireFormat::AnthropicMessages, Backend::Anthropic(anthropic_cfg)), -]); -let map = HashMap::from([("my-model".to_string(), backends)]); -// client.formats_for("my-model") -> the configured formats +use switchyard_llm_client::{ + Backend, HttpBackendConfig, ModelConfig, TranslatingLlmClient, +}; + +fn build_multi_format_client( + openai_chat: HttpBackendConfig, + openai_responses: HttpBackendConfig, + anthropic: HttpBackendConfig, +) -> switchyard_llm_client::Result { + let models = [ModelConfig::new( + "my-model", + Backend::OpenAiChat(openai_chat), + Some(vec![ + Backend::OpenAiResponses(openai_responses), + Backend::Anthropic(anthropic), + ]), + )]; + + TranslatingLlmClient::new(&models) +} ``` ## Headers & auth @@ -152,7 +170,7 @@ let map = HashMap::from([("my-model".to_string(), backends)]); ## Errors -`call` returns [`LlmClientError`]: +`call_rewrite_model` returns [`LlmClientError`]: | Variant | When | |---------|------| @@ -165,10 +183,11 @@ let map = HashMap::from([("my-model".to_string(), backends)]); | `UpstreamHttp { status, body }` | any other non-2xx upstream response | | `Stream(msg)` | mid-stream read / malformed frame | -[`libsy_protocol::Request`]: ../libsy-protocol -[`libsy_protocol::Response`]: ../libsy-protocol +[`switchyard_protocol::Request`]: ../libsy-protocol +[`switchyard_protocol::Response`]: ../libsy-protocol [`libsy-proxy`]: ../libsy-proxy [`Backend`]: src/backend.rs [`HttpBackendConfig`]: src/backend.rs -[`LlmModelClient`]: src/client.rs +[`ModelConfig`]: src/client.rs +[`TranslatingLlmClient::call_rewrite_model`]: src/client.rs [`LlmClientError`]: src/error.rs diff --git a/crates/libsy-llm-client/src/backend.rs b/crates/libsy-llm-client/src/backend.rs index 0512f2ac..c907f290 100644 --- a/crates/libsy-llm-client/src/backend.rs +++ b/crates/libsy-llm-client/src/backend.rs @@ -3,7 +3,7 @@ //! Per-provider backend configuration: wire format, upstream URL, and auth. -use std::collections::BTreeMap; +use std::{collections::BTreeMap, fmt}; use reqwest::RequestBuilder; use switchyard_protocol::WireFormat; @@ -31,7 +31,7 @@ const ANTHROPIC_OVERFLOW_PHRASES: &[&str] = &[ ]; /// Shared HTTP configuration for one upstream backend. -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct HttpBackendConfig { /// Base URL of the provider API (e.g. `https://api.openai.com/v1`). pub base_url: String, @@ -41,6 +41,16 @@ pub struct HttpBackendConfig { pub extra_headers: BTreeMap, } +impl fmt::Debug for HttpBackendConfig { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("HttpBackendConfig") + .field("base_url", &self.base_url) + .field("api_key", &self.api_key.as_ref().map(|_| "[REDACTED]")) + .field("extra_headers", &self.extra_headers) + .finish() + } +} + /// A configured upstream backend, one variant per built-in wire format. /// /// The variant fixes the wire format, URL path, and auth scheme together so no diff --git a/crates/libsy-llm-client/src/client.rs b/crates/libsy-llm-client/src/client.rs index f3bfc3e4..a745ddef 100644 --- a/crates/libsy-llm-client/src/client.rs +++ b/crates/libsy-llm-client/src/client.rs @@ -22,6 +22,7 @@ use crate::backend::Backend; use crate::error::{LlmClientError, Result}; use crate::raw::RawResponse; +// TODO: Why is this here? What does it do? // Headers this client owns or that are hop-by-hop; never forwarded from the // caller's metadata. Auth/version/content-type are set by the backend or the // JSON body, so a forwarded copy would either be ignored or conflict. Compared @@ -32,6 +33,10 @@ const RESERVED_HEADERS: &[&str] = &[ "content-length", "connection", "authorization", + "proxy-authorization", + "proxy-authenticate", + "cookie", + "set-cookie", "x-api-key", "anthropic-version", "content-type", @@ -333,6 +338,7 @@ fn is_reserved_header(name: &str) -> bool { #[cfg(test)] mod tests { use std::collections::BTreeMap; + use std::error::Error; use serde_json::json; use switchyard_protocol::{completion_text, text_request, LlmRequest}; @@ -385,17 +391,10 @@ mod tests { request } - #[test] - fn reserved_headers_are_case_insensitive() { - assert!(is_reserved_header("Authorization")); - assert!(is_reserved_header("content-type")); - assert!(is_reserved_header("X-Api-Key")); - assert!(!is_reserved_header("x-request-id")); - } - #[tokio::test] - async fn missing_model_errors() { - let client = TranslatingLlmClient::new(&[]).unwrap(); + async fn missing_model_errors( + ) -> std::result::Result<(), Box> { + let client = TranslatingLlmClient::new(&[])?; let Err(error) = client .call_rewrite_model(Context::default(), request_for(None, false), None) .await @@ -403,11 +402,13 @@ mod tests { panic!("expected an error"); }; assert!(matches!(error, LlmClientError::MissingModel)); + Ok(()) } #[tokio::test] - async fn unknown_model_errors() { - let client = TranslatingLlmClient::new(&[]).unwrap(); + async fn unknown_model_errors( + ) -> std::result::Result<(), Box> { + let client = TranslatingLlmClient::new(&[])?; let Err(error) = client .call_rewrite_model(Context::default(), request_for(Some("gpt"), false), None) .await @@ -415,12 +416,14 @@ mod tests { panic!("expected an error"); }; assert!(matches!(error, LlmClientError::UnknownModel(model) if model == "gpt")); + Ok(()) } #[tokio::test] - async fn unknown_model_format_errors() { + async fn unknown_model_format_errors( + ) -> std::result::Result<(), Box> { // "gpt" exists but only over OpenAI Chat; the request pins Anthropic. - let client = TranslatingLlmClient::new(&chat_map("https://example.test/v1")).unwrap(); + let client = TranslatingLlmClient::new(&chat_map("https://example.test/v1"))?; let Err(error) = client .call_rewrite_model( Context::default(), @@ -436,11 +439,13 @@ mod tests { LlmClientError::UnknownModelFormat { model, format } if model == "gpt" && format == WireFormat::AnthropicMessages )); + Ok(()) } #[test] - fn backend_for_resolves_configured_format() { - let client = TranslatingLlmClient::new(&chat_map("https://example.test/v1")).unwrap(); + fn backend_for_resolves_configured_format( + ) -> std::result::Result<(), Box> { + let client = TranslatingLlmClient::new(&chat_map("https://example.test/v1"))?; // "gpt" is served over OpenAI Chat only; other formats and models miss. assert!(client.backend_for("gpt", WireFormat::OpenAiChat).is_some()); assert!(client @@ -449,11 +454,13 @@ mod tests { assert!(client .backend_for("missing", WireFormat::OpenAiChat) .is_none()); + Ok(()) } #[tokio::test] - async fn model_name_arg_wins_over_request_model() { - let client = TranslatingLlmClient::new(&[]).unwrap(); + async fn model_name_arg_wins_over_request_model( + ) -> std::result::Result<(), Box> { + let client = TranslatingLlmClient::new(&[])?; // Arg "b" is looked up (and reported), not the request's "a". let Err(error) = client .call_rewrite_model(Context::default(), request_for(Some("a"), false), Some("b")) @@ -462,10 +469,12 @@ mod tests { panic!("expected an error"); }; assert!(matches!(error, LlmClientError::UnknownModel(model) if model == "b")); + Ok(()) } #[tokio::test] - async fn buffered_openai_chat_round_trips() { + async fn buffered_openai_chat_round_trips( + ) -> std::result::Result<(), Box> { let server = MockServer::start().await; Mock::given(method("POST")) .and(path("/v1/chat/completions")) @@ -482,22 +491,20 @@ mod tests { .mount(&server) .await; - let client = TranslatingLlmClient::new(&chat_map(&format!("{}/v1", server.uri()))).unwrap(); + let client = TranslatingLlmClient::new(&chat_map(&format!("{}/v1", server.uri())))?; let response = client .call_rewrite_model(Context::default(), request_for(Some("gpt"), false), None) - .await - .unwrap(); - let agg = response - .llm_response - .into_agg() - .await - .expect("buffered response"); + .await?; + let agg = response.llm_response.into_agg().await?; assert_eq!(completion_text(&agg), "Hi there"); + + Ok(()) } #[tokio::test] - async fn rewrites_model_to_resolved_upstream_id() { + async fn rewrites_model_to_resolved_upstream_id( + ) -> std::result::Result<(), Box> { // Inbound body says "switchyard"; the upstream must receive "gpt". let server = MockServer::start().await; Mock::given(method("POST")) @@ -511,7 +518,7 @@ mod tests { .mount(&server) .await; - let client = TranslatingLlmClient::new(&chat_map(&format!("{}/v1", server.uri()))).unwrap(); + let client = TranslatingLlmClient::new(&chat_map(&format!("{}/v1", server.uri())))?; // Inbound model differs from the map key / resolved model. client .call_rewrite_model( @@ -519,13 +526,14 @@ mod tests { request_for(Some("switchyard"), false), Some("gpt"), ) - .await - .unwrap(); + .await?; // The body_partial_json matcher asserts the upstream saw model "gpt". + Ok(()) } #[tokio::test] - async fn streaming_openai_chat_aggregates() { + async fn streaming_openai_chat_aggregates( + ) -> std::result::Result<(), Box> { let server = MockServer::start().await; let sse = "data: {\"choices\":[{\"delta\":{\"content\":\"Hello\"}}]}\n\n\ data: {\"choices\":[{\"delta\":{\"content\":\" world\"}}]}\n\n\ @@ -536,26 +544,27 @@ mod tests { .mount(&server) .await; - let client = TranslatingLlmClient::new(&chat_map(&format!("{}/v1", server.uri()))).unwrap(); + let client = TranslatingLlmClient::new(&chat_map(&format!("{}/v1", server.uri())))?; let response = client .call_rewrite_model(Context::default(), request_for(Some("gpt"), true), None) - .await - .unwrap(); + .await?; assert!(matches!(response.llm_response, LlmResponse::Stream(_))); - let agg = response.llm_response.into_agg().await.unwrap(); + let agg = response.llm_response.into_agg().await?; assert_eq!(completion_text(&agg), "Hello world"); + Ok(()) } #[tokio::test] - async fn upstream_500_is_upstream_http() { + async fn upstream_500_is_upstream_http( + ) -> std::result::Result<(), Box> { let server = MockServer::start().await; Mock::given(method("POST")) .respond_with(ResponseTemplate::new(500).set_body_string("boom")) .mount(&server) .await; - let client = TranslatingLlmClient::new(&chat_map(&format!("{}/v1", server.uri()))).unwrap(); + let client = TranslatingLlmClient::new(&chat_map(&format!("{}/v1", server.uri())))?; let Err(error) = client .call_rewrite_model(Context::default(), request_for(Some("gpt"), false), None) @@ -567,10 +576,12 @@ mod tests { error, LlmClientError::UpstreamHttp { status: 500, .. } )); + Ok(()) } #[tokio::test] - async fn context_overflow_400_is_mapped() { + async fn context_overflow_400_is_mapped( + ) -> std::result::Result<(), Box> { let server = MockServer::start().await; Mock::given(method("POST")) .respond_with(ResponseTemplate::new(400).set_body_json(json!({ @@ -579,7 +590,7 @@ mod tests { .mount(&server) .await; - let client = TranslatingLlmClient::new(&chat_map(&format!("{}/v1", server.uri()))).unwrap(); + let client = TranslatingLlmClient::new(&chat_map(&format!("{}/v1", server.uri())))?; let Err(error) = client .call_rewrite_model(Context::default(), request_for(Some("gpt"), false), None) @@ -591,10 +602,12 @@ mod tests { error, LlmClientError::ContextWindowExceeded { model, .. } if model == "gpt" )); + Ok(()) } #[tokio::test] - async fn forwards_metadata_headers_except_reserved() { + async fn forwards_metadata_headers_except_reserved( + ) -> std::result::Result<(), Box> { let server = MockServer::start().await; Mock::given(method("POST")) .and(wiremock::matchers::header("x-request-id", "abc")) @@ -628,14 +641,14 @@ mod tests { }), }; - let client = TranslatingLlmClient::new(&chat_map(&format!("{}/v1", server.uri()))).unwrap(); + let client = TranslatingLlmClient::new(&chat_map(&format!("{}/v1", server.uri())))?; // Matchers assert forwarded x-request-id survives and reserved // authorization is the backend's, not the client's. client .call_rewrite_model(Context::default(), request, None) - .await - .unwrap(); + .await?; + Ok(()) } // Minimal `Decision` for driving the client through the `RoutedLlmClient` trait. @@ -656,7 +669,8 @@ mod tests { // Exercises the `RoutedLlmClient` impl: `call` resolves the upstream model from the // decision (the request carries none) and round-trips a buffered response. #[tokio::test] - async fn routed_llm_client_serves_the_decision_model() { + async fn routed_llm_client_serves_the_decision_model( + ) -> std::result::Result<(), Box> { let server = MockServer::start().await; Mock::given(method("POST")) .and(path("/v1/chat/completions")) @@ -673,25 +687,22 @@ mod tests { .mount(&server) .await; - let client = TranslatingLlmClient::new(&chat_map(&format!("{}/v1", server.uri()))).unwrap(); + let client = TranslatingLlmClient::new(&chat_map(&format!("{}/v1", server.uri())))?; let decision: std::sync::Arc = std::sync::Arc::new(FixedDecision("gpt")); // Called through the trait; the request has no model, so "gpt" comes from the decision. let response = client .call(Context::default(), request_for(None, false), decision) - .await - .unwrap(); - let agg = response - .llm_response - .into_agg() - .await - .expect("buffered response"); + .await?; + let agg = response.llm_response.into_agg().await?; assert_eq!(completion_text(&agg), "routed hi"); + Ok(()) } // Raw path, buffered: decode an OpenAI Chat body -> call -> encode back to OpenAI // Chat JSON, with the client-facing `model` restamped over the upstream id. #[tokio::test] - async fn call_rewrite_model_raw_round_trips_buffered_json() { + async fn call_rewrite_model_raw_round_trips_buffered_json( + ) -> std::result::Result<(), Box> { let server = MockServer::start().await; Mock::given(method("POST")) .and(path("/v1/chat/completions")) @@ -708,7 +719,7 @@ mod tests { .mount(&server) .await; - let client = TranslatingLlmClient::new(&chat_map(&format!("{}/v1", server.uri()))).unwrap(); + let client = TranslatingLlmClient::new(&chat_map(&format!("{}/v1", server.uri())))?; let raw = json!({ "model": "client-facing", "messages": [{"role": "user", "content": "hi"}] @@ -721,8 +732,7 @@ mod tests { Some("gpt"), WireFormat::OpenAiChat, ) - .await - .unwrap() + .await? else { panic!("expected a buffered response"); }; @@ -730,13 +740,15 @@ mod tests { assert_eq!(body["choices"][0]["message"]["content"], "Hi there"); // The client sees the model it asked for, not the upstream "gpt". assert_eq!(body["model"], "client-facing"); + Ok(()) } // Raw path, streaming: an inbound `stream: true` request yields an unframed stream // of OpenAI Chat chunk objects whose deltas reassemble the completion. #[tokio::test] - async fn call_rewrite_model_raw_streams_wire_events() { - use futures::StreamExt; + async fn call_rewrite_model_raw_streams_wire_events( + ) -> std::result::Result<(), Box> { + use futures::TryStreamExt; let server = MockServer::start().await; let sse = "data: {\"choices\":[{\"delta\":{\"content\":\"Hello\"}}]}\n\n\ @@ -748,7 +760,7 @@ mod tests { .mount(&server) .await; - let client = TranslatingLlmClient::new(&chat_map(&format!("{}/v1", server.uri()))).unwrap(); + let client = TranslatingLlmClient::new(&chat_map(&format!("{}/v1", server.uri())))?; let raw = json!({ "model": "client-facing", "messages": [{"role": "user", "content": "hi"}], @@ -762,24 +774,25 @@ mod tests { Some("gpt"), WireFormat::OpenAiChat, ) - .await - .unwrap() + .await? else { panic!("expected a streamed response"); }; - let events: Vec = stream.map(|item| item.unwrap()).collect().await; + let events: Vec = stream.try_collect().await?; assert!(!events.is_empty(), "expected at least one wire event"); let content: String = events .iter() .filter_map(|event| event["choices"][0]["delta"]["content"].as_str()) .collect(); assert_eq!(content, "Hello world"); + Ok(()) } // Raw path forwards caller headers (minus the reserved set) to the upstream. #[tokio::test] - async fn call_rewrite_model_raw_forwards_headers() { + async fn call_rewrite_model_raw_forwards_headers( + ) -> std::result::Result<(), Box> { let server = MockServer::start().await; Mock::given(method("POST")) .and(wiremock::matchers::header("x-request-id", "abc")) @@ -797,7 +810,7 @@ mod tests { headers.insert("x-request-id".to_string(), "abc".to_string()); headers.insert("authorization".to_string(), "Bearer client-key".to_string()); - let client = TranslatingLlmClient::new(&chat_map(&format!("{}/v1", server.uri()))).unwrap(); + let client = TranslatingLlmClient::new(&chat_map(&format!("{}/v1", server.uri())))?; let raw = json!({"model": "gpt", "messages": [{"role": "user", "content": "hi"}]}); // Matchers assert the forwarded x-request-id survives and reserved // authorization is the backend's, not the client's. @@ -809,7 +822,7 @@ mod tests { Some("gpt"), WireFormat::OpenAiChat, ) - .await - .unwrap(); + .await?; + Ok(()) } } diff --git a/crates/libsy-llm-client/src/lib.rs b/crates/libsy-llm-client/src/lib.rs index 97fe5b3a..22a1c472 100644 --- a/crates/libsy-llm-client/src/lib.rs +++ b/crates/libsy-llm-client/src/lib.rs @@ -10,13 +10,6 @@ //! HTTP call with a shared [`reqwest::Client`], and decodes the wire response //! back to a [`switchyard_protocol::Response`] — supporting both buffered and //! streamed responses. -//! -//! The crate depends only on `libsy-protocol` and `switchyard-translation`. Neutral -//! IR encode/decode — including SSE stream decoding — lives in -//! `switchyard-translation`; this crate is the HTTP transport around it. The -//! context-overflow detection in [`mod@error`] is vendored from -//! `switchyard-components` (whose copy is crate-private and unavailable here); a -//! future refactor could promote it to a shared location. pub mod backend; pub mod client; From 4ec1acfc8b672405cbf6fd5d3e4cebf81a573b52 Mon Sep 17 00:00:00 2001 From: Graham King Date: Tue, 21 Jul 2026 10:37:01 -0400 Subject: [PATCH 3/3] Dependency cleanup Move one dep to dev-dependencies, remove another. Signed-off-by: Graham King --- Cargo.lock | 2461 ++++++++++++++++++++++++++++ crates/libsy-llm-client/Cargo.toml | 5 +- crates/libsy-llm-client/README.md | 2 +- 3 files changed, 2464 insertions(+), 4 deletions(-) create mode 100644 Cargo.lock diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..9662c355 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2461 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "arc-swap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b" +dependencies = [ + "rustversion", +] + +[[package]] +name = "assert-json-diff" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "aws-lc-rs" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4342d8937fc7e5dd9b1c60292261c0670c882a2cd1719cfc11b1af41731e32ad" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9ceb1da931507a12f4fccea479dccd00da1943e1b4ae72d8e502d707361444" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", + "pkg-config", +] + +[[package]] +name = "axum" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" +dependencies = [ + "axum-core", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-server" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1df331683d982a0b9492b38127151e6453639cd34926eb9c07d4cd8c6d22bfc" +dependencies = [ + "arc-swap", + "bytes", + "either", + "fs-err", + "http", + "http-body", + "hyper", + "hyper-util", + "pin-project-lite", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "cc" +version = "1.2.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17dd265a7d0f31ef544e1b20e03add05d3b45b491b633b10d67145d2acc1a38" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "chacha20" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +dependencies = [ + "cfg-if", + "cpufeatures", + "rand_core 0.10.1", +] + +[[package]] +name = "clap" +version = "4.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd059f9da4f5c36b3787f65d38ccaab1cc315f07b01f89abc8359ee6a8205011" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "deadpool" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0be2b1d1d6ec8d846f05e137292d0b89133caf95ef33695424c09568bdd39b1b" +dependencies = [ + "deadpool-runtime", + "lazy_static", + "num_cpus", + "tokio", +] + +[[package]] +name = "deadpool-runtime" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b" + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "displaydoc" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + +[[package]] +name = "equivalent" +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 = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs-err" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91aa448ca50d7e79433bdf3ee8d99215430d2ec02ade5aefab2a073a1822e8a" +dependencies = [ + "autocfg", + "tokio", +] + +[[package]] +name = "fs_extra" +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-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "rand_core 0.10.1", + "wasm-bindgen", +] + +[[package]] +name = "h2" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "http" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-native-certs", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libsy-examples" +version = "0.1.0" +dependencies = [ + "async-trait", + "futures", + "switchyard-libsy", + "switchyard-protocol", + "tokio", + "tokio-stream", +] + +[[package]] +name = "libyaml-rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e126dda6f34391ab7b444f9922055facc83c07a910da3eb16f1e4d9c45dc777" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mio" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pyo3" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91fd8e38a3b50ed1167fb981cd6fd60147e091784c427b8f7183a7ee32c31c12" +dependencies = [ + "libc", + "once_cell", + "portable-atomic", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", +] + +[[package]] +name = "pyo3-async-runtimes" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e7364a95bf00e8377bbf9b0f09d7ff9715a29d8fcf93b47d1a967363b973178" +dependencies = [ + "futures-channel", + "futures-util", + "once_cell", + "pin-project-lite", + "pyo3", + "tokio", +] + +[[package]] +name = "pyo3-build-config" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e368e7ddfdeb98c9bca7f8383be1648fd84ab466bf2bc015e94008db6d35611e" +dependencies = [ + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f29e10af80b1f7ccaf7f69eace800a03ecd13e883acfacc1e5d0988605f651e" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df6e520eff47c45997d2fc7dd8214b25dd1310918bbb2642156ef66a67f29813" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4cdc218d835738f81c2338f822078af45b4afdf8b2e33cbb5916f108b813acb" +dependencies = [ + "heck", + "proc-macro2", + "pyo3-build-config", + "quote", + "syn", +] + +[[package]] +name = "pythonize" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b79f670c9626c8b651c0581011b57b6ba6970bb69faf01a7c4c0cfc81c43f95" +dependencies = [ + "pyo3", + "serde", +] + +[[package]] +name = "quinn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560" +dependencies = [ + "bytes", + "getrandom 0.4.3", + "lru-slab", + "rand 0.10.2", + "rand_pcg", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.61.2", +] + +[[package]] +name = "quote" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" +dependencies = [ + "libc", + "rand_chacha", + "rand_core 0.6.4", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core 0.10.1", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustls" +version = "0.23.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "shlex" +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 = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "switchyard-components" +version = "0.1.0" +dependencies = [ + "async-stream", + "async-trait", + "futures-util", + "parking_lot", + "rand 0.8.7", + "reqwest", + "serde", + "serde_json", + "switchyard-core", + "switchyard-translation", + "tokio", + "tracing", +] + +[[package]] +name = "switchyard-components-v2" +version = "0.1.0" +dependencies = [ + "async-stream", + "async-trait", + "futures-core", + "futures-util", + "parking_lot", + "rand 0.8.7", + "reqwest", + "serde", + "serde_json", + "switchyard-components", + "switchyard-components-v2-macros", + "switchyard-core", + "tokio", + "toml", + "tracing", + "yaml_serde", +] + +[[package]] +name = "switchyard-components-v2-macros" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "switchyard-core" +version = "0.1.0" +dependencies = [ + "async-trait", + "futures-core", + "lru", + "rand 0.8.7", + "serde", + "serde_json", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "switchyard-libsy" +version = "0.1.0" +dependencies = [ + "async-trait", + "futures", + "rand 0.8.7", + "serde_json", + "switchyard-protocol", + "tokio", + "tokio-stream", +] + +[[package]] +name = "switchyard-llm-client" +version = "0.1.0" +dependencies = [ + "async-trait", + "futures", + "futures-util", + "reqwest", + "serde_json", + "switchyard-protocol", + "switchyard-translation", + "thiserror", + "tokio", + "wiremock", +] + +[[package]] +name = "switchyard-protocol" +version = "0.1.0" +dependencies = [ + "async-trait", + "futures", + "serde", + "serde_json", +] + +[[package]] +name = "switchyard-py" +version = "0.1.0" +dependencies = [ + "async-trait", + "futures-util", + "pyo3", + "pyo3-async-runtimes", + "pythonize", + "serde", + "serde_json", + "switchyard-components", + "switchyard-components-v2", + "switchyard-core", + "switchyard-libsy", + "switchyard-server", + "switchyard-translation", + "tokio", + "tracing", +] + +[[package]] +name = "switchyard-server" +version = "0.1.0" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "axum-server", + "clap", + "futures-util", + "http-body-util", + "rustls", + "serde", + "serde_json", + "switchyard-components-v2", + "switchyard-core", + "switchyard-translation", + "tokio", + "tower", + "tracing", +] + +[[package]] +name = "switchyard-skill-distillation" +version = "0.1.0" +dependencies = [ + "async-trait", + "serde", + "serde_json", + "thiserror", + "tokio", +] + +[[package]] +name = "switchyard-translation" +version = "0.1.0" +dependencies = [ + "async-stream", + "futures", + "pretty_assertions", + "serde", + "serde_json", + "switchyard-protocol", + "thiserror", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "target-lexicon" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317fafbbe3f02fc663dad00ea6186197de963cd4190e86a26d8d0fae095539af" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + +[[package]] +name = "wiremock" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08db1edfb05d9b3c1542e521aea074442088292f00b5f28e435c714a98f85031" +dependencies = [ + "assert-json-diff", + "base64", + "deadpool", + "futures", + "http", + "http-body-util", + "hyper", + "hyper-util", + "log", + "once_cell", + "regex", + "serde", + "serde_json", + "tokio", + "url", +] + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "yaml_serde" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c7c1b1a6a7c8a6b2741a6c21a4f8918e51899b111cfa08d1288202656e3975" +dependencies = [ + "indexmap", + "itoa", + "libyaml-rs", + "ryu", + "serde", +] + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/crates/libsy-llm-client/Cargo.toml b/crates/libsy-llm-client/Cargo.toml index b930adcb..67c33893 100644 --- a/crates/libsy-llm-client/Cargo.toml +++ b/crates/libsy-llm-client/Cargo.toml @@ -12,16 +12,15 @@ repository.workspace = true rust-version.workspace = true [dependencies] -switchyard-protocol = { path = "../libsy-protocol", version = "0.1.0" } +switchyard-protocol = { path = "../protocol", version = "0.1.0" } switchyard-translation = { path = "../switchyard-translation", version = "0.1.0" } reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls-native-roots", "stream"] } async-trait = "0.1" -futures = "0.3" futures-util = "0.3" serde_json = "1" thiserror = "2" [dev-dependencies] -pretty_assertions = "1" +futures = "0.3" tokio = { version = "1", features = ["macros", "rt-multi-thread"] } wiremock = "0.6" diff --git a/crates/libsy-llm-client/README.md b/crates/libsy-llm-client/README.md index b4d31a9c..e769123f 100644 --- a/crates/libsy-llm-client/README.md +++ b/crates/libsy-llm-client/README.md @@ -12,7 +12,7 @@ headers, makes the call with a shared `reqwest::Client`, and decodes the reply back into a [`switchyard_protocol::Response`] — buffered or streamed. It depends only on `switchyard-protocol` and `switchyard-translation`; no server, no -provider SDK. For an HTTP front door built on top of it, see [`libsy-proxy`]. +provider SDK. ## Concepts