Unverified Commit fd5cc288 authored by ishandhanani's avatar ishandhanani Committed by GitHub
Browse files

refactor(3/3): switch dynamo-protocols to upstream async-openai types (#7625)


Co-authored-by: default avatarDmitry Tokarev <dtokarev@nvidia.com>
parent d517fb80
......@@ -146,9 +146,9 @@ dependencies = [
[[package]]
name = "arc-swap"
version = "1.8.2"
version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9f3647c145568cec02c42054e07bdf9a5a698e15b466fb2341bfc393cd24aa5"
checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207"
dependencies = [
"rustversion",
]
......@@ -263,14 +263,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4288f83726785267c6f2ef073a3d83dc3f9b81464e9f99898240cced85fce35a"
[[package]]
name = "async-openai-macros"
version = "0.1.1"
name = "async-openai"
version = "0.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81872a8e595e8ceceab71c6ba1f9078e313b452a1e31934e6763ef5d308705e4"
checksum = "ec08254d61379df136135d3d1ac04301be7699fd7d9e57655c63ac7d650a6922"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"bytes",
"derive_builder",
"getrandom 0.3.4",
"serde",
"serde_json",
]
[[package]]
......@@ -383,9 +385,9 @@ dependencies = [
[[package]]
name = "aws-lc-rs"
version = "1.16.1"
version = "1.16.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94bffc006df10ac2a68c83692d734a465f8ee6c5b384d8545a636f81d858f4bf"
checksum = "a054912289d18629dc78375ba2c3726a3afe3ff71b4edba9dedfca0e3446d1fc"
dependencies = [
"aws-lc-sys",
"zeroize",
......@@ -393,9 +395,9 @@ dependencies = [
[[package]]
name = "aws-lc-sys"
version = "0.38.0"
version = "0.39.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4321e568ed89bb5a7d291a7f37997c2c0df89809d7b6d12062c81ddb54aa782e"
checksum = "83a25cf98105baa966497416dbd42565ce3a8cf8dbfd59803ec9ad46f3126399"
dependencies = [
"cc",
"cmake",
......@@ -444,7 +446,7 @@ dependencies = [
"http 1.4.0",
"http-body 1.0.1",
"http-body-util",
"hyper 1.8.1",
"hyper 1.9.0",
"hyper-util",
"itoa",
"matchit 0.8.4",
......@@ -526,7 +528,7 @@ dependencies = [
"fs-err",
"http 1.4.0",
"http-body 1.0.1",
"hyper 1.8.1",
"hyper 1.9.0",
"hyper-util",
"pin-project-lite",
"rustls",
......@@ -537,20 +539,6 @@ dependencies = [
"tower-service",
]
[[package]]
name = "backoff"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1"
dependencies = [
"futures-core",
"getrandom 0.2.17",
"instant",
"pin-project-lite",
"rand 0.8.5",
"tokio",
]
[[package]]
name = "backon"
version = "1.6.0"
......@@ -648,7 +636,7 @@ dependencies = [
"proc-macro2",
"quote",
"regex",
"rustc-hash 2.1.1",
"rustc-hash 2.1.2",
"shlex",
"syn 2.0.117",
]
......@@ -715,16 +703,16 @@ dependencies = [
[[package]]
name = "blake3"
version = "1.8.3"
version = "1.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d"
checksum = "4d2d5991425dfd0785aed03aedcf0b321d61975c9b5b3689c774a2610ae0b51e"
dependencies = [
"arrayref",
"arrayvec",
"cc",
"cfg-if",
"constant_time_eq",
"cpufeatures",
"cpufeatures 0.3.0",
"memmap2",
"rayon-core",
]
......@@ -846,7 +834,7 @@ checksum = "3fce8dd7fcfcbf3a0a87d8f515194b49d6135acab73e18bd380d1d93bb1a15eb"
dependencies = [
"clap 4.6.0",
"heck 0.4.1",
"indexmap 2.13.0",
"indexmap 2.13.1",
"log",
"proc-macro2",
"quote",
......@@ -859,9 +847,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.2.57"
version = "1.2.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423"
checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283"
dependencies = [
"find-msvc-tools",
"jobserver",
......@@ -1005,9 +993,9 @@ checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
[[package]]
name = "cmake"
version = "0.1.57"
version = "0.1.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d"
checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678"
dependencies = [
"cc",
]
......@@ -1059,9 +1047,9 @@ dependencies = [
[[package]]
name = "config"
version = "0.15.21"
version = "0.15.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fe5feec195269515c4722937cd7ffcfe7b4205d18d2e6577b7223ecb159ab00"
checksum = "8e68cfe19cd7d23ffde002c24ffa5cda73931913ef394d5eaaa32037dc940c0c"
dependencies = [
"async-trait",
"convert_case",
......@@ -1072,8 +1060,8 @@ dependencies = [
"serde-untagged",
"serde_core",
"serde_json",
"toml 1.0.6+spec-1.1.0",
"winnow",
"toml 1.1.2+spec-1.1.0",
"winnow 1.0.1",
"yaml-rust2",
]
......@@ -1226,6 +1214,15 @@ dependencies = [
"libc",
]
[[package]]
name = "cpufeatures"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
dependencies = [
"libc",
]
[[package]]
name = "crc32fast"
version = "1.5.0"
......@@ -1429,7 +1426,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
dependencies = [
"cfg-if",
"cpufeatures",
"cpufeatures 0.2.17",
"curve25519-dalek-derive",
"digest",
"fiat-crypto",
......@@ -1450,9 +1447,9 @@ dependencies = [
[[package]]
name = "daachorse"
version = "1.0.0"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63b7ef7a4be509357f4804d0a22e830daddb48f19fd604e4ad32ddce04a94c36"
checksum = "6f55d7153ba3b507595872a3874803f07a8a81d1e888abed8e5db7da0597d6e2"
[[package]]
name = "darling"
......@@ -1746,9 +1743,9 @@ dependencies = [
[[package]]
name = "dircpy"
version = "0.3.19"
version = "0.3.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a88521b0517f5f9d51d11925d8ab4523497dcf947073fa3231a311b63941131c"
checksum = "ebcbec2b9a580ddee352ac38523d2ecd4dcaad53532957034394556909e27f4b"
dependencies = [
"jwalk",
"log",
......@@ -1878,7 +1875,7 @@ dependencies = [
"rmp-serde",
"rstest 0.18.2",
"rstest_reuse",
"rustc-hash 2.1.1",
"rustc-hash 2.1.2",
"serde",
"serde_json",
"thiserror 2.0.18",
......@@ -2023,7 +2020,7 @@ dependencies = [
"ndarray-npy",
"rand 0.9.2",
"rstest 0.18.2",
"rustc-hash 2.1.1",
"rustc-hash 2.1.2",
"serde",
"serde_json",
"slotmap",
......@@ -2058,28 +2055,16 @@ dependencies = [
name = "dynamo-protocols"
version = "1.0.0"
dependencies = [
"async-openai-macros",
"backoff",
"base64 0.22.1",
"bytes",
"async-openai",
"derive_builder",
"eventsource-stream",
"futures",
"rand 0.9.2",
"reqwest 0.12.28",
"reqwest-eventsource",
"secrecy",
"serde",
"serde_json",
"thiserror 2.0.18",
"tokio",
"tokio-stream",
"tokio-test",
"tokio-tungstenite",
"tokio-util",
"tracing",
"url",
"utoipa",
"uuid",
]
......@@ -2344,17 +2329,6 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "eventsource-stream"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74fef4569247a5f429d9156b9d0a2599914385dd189c539334c625d8099d90ab"
dependencies = [
"futures-core",
"nom 7.1.3",
"pin-project-lite",
]
[[package]]
name = "exr"
version = "1.74.0"
......@@ -2423,11 +2397,11 @@ dependencies = [
[[package]]
name = "fastrand"
version = "2.3.0"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
checksum = "a043dc74da1e37d6afe657061213aa6f425f855399a11d3463c6ecccc4dfda1f"
dependencies = [
"getrandom 0.2.17",
"getrandom 0.3.4",
]
[[package]]
......@@ -2843,7 +2817,7 @@ dependencies = [
"futures-sink",
"futures-util",
"http 0.2.12",
"indexmap 2.13.0",
"indexmap 2.13.1",
"slab",
"tokio",
"tokio-util",
......@@ -2862,7 +2836,7 @@ dependencies = [
"futures-core",
"futures-sink",
"http 1.4.0",
"indexmap 2.13.0",
"indexmap 2.13.1",
"slab",
"tokio",
"tokio-util",
......@@ -3123,9 +3097,9 @@ dependencies = [
[[package]]
name = "hyper"
version = "1.8.1"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11"
checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca"
dependencies = [
"atomic-waker",
"bytes",
......@@ -3138,7 +3112,6 @@ dependencies = [
"httpdate",
"itoa",
"pin-project-lite",
"pin-utils",
"smallvec",
"tokio",
"want",
......@@ -3151,7 +3124,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
dependencies = [
"http 1.4.0",
"hyper 1.8.1",
"hyper 1.9.0",
"hyper-util",
"log",
"rustls",
......@@ -3169,7 +3142,7 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0"
dependencies = [
"hyper 1.8.1",
"hyper 1.9.0",
"hyper-util",
"pin-project-lite",
"tokio",
......@@ -3188,7 +3161,7 @@ dependencies = [
"futures-util",
"http 1.4.0",
"http-body 1.0.1",
"hyper 1.8.1",
"hyper 1.9.0",
"ipnet",
"libc",
"percent-encoding",
......@@ -3227,12 +3200,13 @@ dependencies = [
[[package]]
name = "icu_collections"
version = "2.1.1"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c"
dependencies = [
"displaydoc",
"potential_utf",
"utf8_iter",
"yoke",
"zerofrom",
"zerovec",
......@@ -3240,9 +3214,9 @@ dependencies = [
[[package]]
name = "icu_locale_core"
version = "2.1.1"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29"
dependencies = [
"displaydoc",
"litemap",
......@@ -3253,9 +3227,9 @@ dependencies = [
[[package]]
name = "icu_normalizer"
version = "2.1.1"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599"
checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4"
dependencies = [
"icu_collections",
"icu_normalizer_data",
......@@ -3270,15 +3244,15 @@ dependencies = [
[[package]]
name = "icu_normalizer_data"
version = "2.1.1"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38"
[[package]]
name = "icu_properties"
version = "2.1.2"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec"
checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de"
dependencies = [
"icu_collections",
"icu_locale_core",
......@@ -3290,15 +3264,15 @@ dependencies = [
[[package]]
name = "icu_properties_data"
version = "2.1.2"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af"
checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14"
[[package]]
name = "icu_provider"
version = "2.1.1"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421"
dependencies = [
"displaydoc",
"icu_locale_core",
......@@ -3396,9 +3370,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.13.0"
version = "2.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff"
dependencies = [
"equivalent",
"hashbrown 0.16.1",
......@@ -3460,11 +3434,11 @@ dependencies = [
[[package]]
name = "insta"
version = "1.46.3"
version = "1.47.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e82db8c87c7f1ccecb34ce0c24399b8a73081427f3c7c50a5d597925356115e4"
checksum = "7b4a6248eb93a4401ed2f37dfe8ea592d3cf05b7cf4f8efa867b6895af7e094e"
dependencies = [
"console 0.15.11",
"console 0.16.3",
"globset",
"once_cell",
"pest",
......@@ -3476,15 +3450,6 @@ dependencies = [
"walkdir",
]
[[package]]
name = "instant"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
dependencies = [
"cfg-if",
]
[[package]]
name = "interpolate_name"
version = "0.2.4"
......@@ -3504,9 +3469,9 @@ checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
[[package]]
name = "iri-string"
version = "0.7.10"
version = "0.7.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a"
checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20"
dependencies = [
"memchr",
"serde",
......@@ -3588,9 +3553,9 @@ dependencies = [
[[package]]
name = "itoa"
version = "1.0.17"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "jiff"
......@@ -3645,10 +3610,12 @@ dependencies = [
[[package]]
name = "js-sys"
version = "0.3.91"
version = "0.3.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c"
checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9"
dependencies = [
"cfg-if",
"futures-util",
"once_cell",
"wasm-bindgen",
]
......@@ -3809,7 +3776,7 @@ dependencies = [
"http 1.4.0",
"http-body 1.0.1",
"http-body-util",
"hyper 1.8.1",
"hyper 1.9.0",
"hyper-rustls",
"hyper-timeout",
"hyper-util",
......@@ -3920,7 +3887,7 @@ dependencies = [
"derive_builder",
"dynamo-tokens",
"futures",
"indexmap 2.13.0",
"indexmap 2.13.1",
"lru 0.16.3",
"parking_lot",
"prometheus",
......@@ -3992,9 +3959,9 @@ checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8"
[[package]]
name = "libc"
version = "0.2.183"
version = "0.2.184"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af"
[[package]]
name = "libdynamo_llm"
......@@ -4052,9 +4019,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
[[package]]
name = "libredox"
version = "0.1.14"
version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a"
checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08"
dependencies = [
"bitflags 2.11.0",
"libc",
......@@ -4076,15 +4043,15 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
[[package]]
name = "litemap"
version = "0.8.1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
[[package]]
name = "local-ip-address"
version = "0.6.10"
version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79ef8c257c92ade496781a32a581d43e3d512cf8ce714ecf04ea80f93ed0ff4a"
checksum = "d4a59a0cb1c7f84471ad5cd38d768c2a29390d17f1ff2827cdf49bc53e8ac70b"
dependencies = [
"libc",
"neli",
......@@ -4321,20 +4288,19 @@ dependencies = [
[[package]]
name = "minijinja"
version = "2.17.1"
version = "2.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ea5ea1e90055f200af6b8e52a4a34e05e77e7fee953a9fb40c631efdc43cab1"
checksum = "805bfd7352166bae857ee569628b52bcd85a1cecf7810861ebceb1686b72b75d"
dependencies = [
"memo-map",
"self_cell",
"serde",
]
[[package]]
name = "minijinja-contrib"
version = "2.17.1"
version = "2.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2fce60cb2e26ba7ddd485c8f5d3d635535e465c195bfb4af85971b428a985d0"
checksum = "45092d80391870622fcf3bd82f5d2af18f99533ea60debb4bc9db0c76f0e809a"
dependencies = [
"minijinja",
"serde",
......@@ -4380,9 +4346,9 @@ dependencies = [
[[package]]
name = "mio"
version = "1.1.1"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
dependencies = [
"libc",
"wasi",
......@@ -4402,7 +4368,7 @@ dependencies = [
"http 1.4.0",
"http-body 1.0.1",
"http-body-util",
"hyper 1.8.1",
"hyper 1.9.0",
"hyper-util",
"log",
"pin-project-lite",
......@@ -4754,9 +4720,9 @@ dependencies = [
[[package]]
name = "num-conv"
version = "0.2.0"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050"
checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967"
[[package]]
name = "num-derive"
......@@ -5010,7 +4976,7 @@ dependencies = [
"http-body-util",
"httparse",
"humantime",
"hyper 1.8.1",
"hyper 1.9.0",
"itertools 0.14.0",
"md-5",
"parking_lot",
......@@ -5165,9 +5131,9 @@ dependencies = [
[[package]]
name = "opentelemetry-otlp"
version = "0.31.0"
version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2366db2dca4d2ad033cad11e6ee42844fd727007af5ad04a1730f4cb8163bf"
checksum = "1f69cd6acbb9af919df949cd1ec9e5e7fdc2ef15d234b6b795aaa525cc02f71f"
dependencies = [
"http 1.4.0",
"opentelemetry",
......@@ -5429,7 +5395,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772"
dependencies = [
"fixedbitset",
"indexmap 2.13.0",
"indexmap 2.13.1",
]
[[package]]
......@@ -5440,7 +5406,7 @@ checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455"
dependencies = [
"fixedbitset",
"hashbrown 0.15.5",
"indexmap 2.13.0",
"indexmap 2.13.1",
]
[[package]]
......@@ -5593,9 +5559,9 @@ dependencies = [
[[package]]
name = "potential_utf"
version = "0.1.4"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77"
checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564"
dependencies = [
"zerovec",
]
......@@ -5631,7 +5597,7 @@ version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
dependencies = [
"toml_edit 0.25.4+spec-1.1.0",
"toml_edit 0.25.10+spec-1.1.0",
]
[[package]]
......@@ -5714,9 +5680,9 @@ dependencies = [
[[package]]
name = "proptest"
version = "1.10.0"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37566cb3fdacef14c0737f9546df7cfeadbfbc9fef10991038bf5015d0c80532"
checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744"
dependencies = [
"bit-set 0.8.0",
"bit-vec 0.8.0",
......@@ -5757,7 +5723,7 @@ version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf"
dependencies = [
"heck 0.4.1",
"heck 0.5.0",
"itertools 0.14.0",
"log",
"multimap",
......@@ -5777,7 +5743,7 @@ version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7"
dependencies = [
"heck 0.4.1",
"heck 0.5.0",
"itertools 0.14.0",
"log",
"multimap",
......@@ -5858,9 +5824,9 @@ dependencies = [
[[package]]
name = "pulldown-cmark"
version = "0.13.1"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83c41efbf8f90ac44de7f3a868f0867851d261b56291732d0cbf7cceaaeb55a6"
checksum = "7c3a14896dfa883796f1cb410461aef38810ea05f2b2c33c5aded3649095fdad"
dependencies = [
"bitflags 2.11.0",
"memchr",
......@@ -5937,7 +5903,7 @@ dependencies = [
"pin-project-lite",
"quinn-proto",
"quinn-udp",
"rustc-hash 2.1.1",
"rustc-hash 2.1.2",
"rustls",
"socket2 0.6.3",
"thiserror 2.0.18",
......@@ -5957,7 +5923,7 @@ dependencies = [
"lru-slab",
"rand 0.9.2",
"ring",
"rustc-hash 2.1.1",
"rustc-hash 2.1.2",
"rustls",
"rustls-pki-types",
"slab",
......@@ -6303,7 +6269,7 @@ dependencies = [
"http 1.4.0",
"http-body 1.0.1",
"http-body-util",
"hyper 1.8.1",
"hyper 1.9.0",
"hyper-rustls",
"hyper-util",
"js-sys",
......@@ -6334,22 +6300,6 @@ dependencies = [
"webpki-roots 1.0.6",
]
[[package]]
name = "reqwest-eventsource"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "632c55746dbb44275691640e7b40c907c16a2dc1a5842aa98aaec90da6ec6bde"
dependencies = [
"eventsource-stream",
"futures-core",
"futures-timer",
"mime",
"nom 7.1.3",
"pin-project-lite",
"reqwest 0.12.28",
"thiserror 1.0.69",
]
[[package]]
name = "rgb"
version = "0.8.53"
......@@ -6391,9 +6341,9 @@ dependencies = [
[[package]]
name = "ron"
version = "0.12.0"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd490c5b18261893f14449cbd28cb9c0b637aebf161cd77900bfdedaff21ec32"
checksum = "4147b952f3f819eca0e99527022f7d6a8d05f111aeb0a62960c74eb283bec8fc"
dependencies = [
"bitflags 2.11.0",
"once_cell",
......@@ -6584,9 +6534,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustc-hash"
version = "2.1.1"
version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
[[package]]
name = "rustc_version"
......@@ -6634,7 +6584,7 @@ dependencies = [
"once_cell",
"ring",
"rustls-pki-types",
"rustls-webpki 0.103.9",
"rustls-webpki 0.103.10",
"subtle",
"zeroize",
]
......@@ -6695,9 +6645,9 @@ dependencies = [
[[package]]
name = "rustls-webpki"
version = "0.103.9"
version = "0.103.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53"
checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef"
dependencies = [
"aws-lc-rs",
"ring",
......@@ -6868,7 +6818,6 @@ version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a"
dependencies = [
"serde",
"zeroize",
]
......@@ -6908,17 +6857,11 @@ dependencies = [
"libc",
]
[[package]]
name = "self_cell"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b12e76d157a900eb52e81bc6e9f3069344290341720e9178cde2407113ac8d89"
[[package]]
name = "semver"
version = "1.0.27"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
[[package]]
name = "serde"
......@@ -7009,7 +6952,7 @@ version = "1.0.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
dependencies = [
"indexmap 2.13.0",
"indexmap 2.13.1",
"itoa",
"memchr",
"serde",
......@@ -7059,9 +7002,9 @@ dependencies = [
[[package]]
name = "serde_spanned"
version = "1.0.4"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776"
checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
dependencies = [
"serde_core",
]
......@@ -7088,7 +7031,7 @@ dependencies = [
"chrono",
"hex",
"indexmap 1.9.3",
"indexmap 2.13.0",
"indexmap 2.13.1",
"schemars 0.9.0",
"schemars 1.2.1",
"serde_core",
......@@ -7115,7 +7058,7 @@ version = "0.9.34+deprecated"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
dependencies = [
"indexmap 2.13.0",
"indexmap 2.13.1",
"itoa",
"ryu",
"serde",
......@@ -7155,7 +7098,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
dependencies = [
"cfg-if",
"cpufeatures",
"cpufeatures 0.2.17",
"digest",
]
......@@ -7166,7 +7109,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
"cfg-if",
"cpufeatures",
"cpufeatures 0.2.17",
"digest",
]
......@@ -7225,9 +7168,9 @@ dependencies = [
[[package]]
name = "simd-adler32"
version = "0.3.8"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
[[package]]
name = "simd_helpers"
......@@ -7660,9 +7603,9 @@ dependencies = [
[[package]]
name = "tinystr"
version = "0.8.2"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869"
checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d"
dependencies = [
"displaydoc",
"zerovec",
......@@ -7748,7 +7691,7 @@ checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
dependencies = [
"bytes",
"libc",
"mio 1.1.1",
"mio 1.2.0",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
......@@ -7825,18 +7768,6 @@ dependencies = [
"tokio",
]
[[package]]
name = "tokio-tungstenite"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084"
dependencies = [
"futures-util",
"log",
"tokio",
"tungstenite",
]
[[package]]
name = "tokio-util"
version = "0.7.18"
......@@ -7887,15 +7818,15 @@ dependencies = [
[[package]]
name = "toml"
version = "1.0.6+spec-1.1.0"
version = "1.1.2+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "399b1124a3c9e16766831c6bba21e50192572cdd98706ea114f9502509686ffc"
checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee"
dependencies = [
"serde_core",
"serde_spanned 1.0.4",
"toml_datetime 1.0.0+spec-1.1.0",
"serde_spanned 1.1.1",
"toml_datetime 1.1.1+spec-1.1.0",
"toml_parser",
"winnow",
"winnow 1.0.1",
]
[[package]]
......@@ -7909,9 +7840,9 @@ dependencies = [
[[package]]
name = "toml_datetime"
version = "1.0.0+spec-1.1.0"
version = "1.1.1+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e"
checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
dependencies = [
"serde_core",
]
......@@ -7922,33 +7853,33 @@ version = "0.22.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
dependencies = [
"indexmap 2.13.0",
"indexmap 2.13.1",
"serde",
"serde_spanned 0.6.9",
"toml_datetime 0.6.11",
"toml_write",
"winnow",
"winnow 0.7.15",
]
[[package]]
name = "toml_edit"
version = "0.25.4+spec-1.1.0"
version = "0.25.10+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2"
checksum = "a82418ca169e235e6c399a84e395ab6debeb3bc90edc959bf0f48647c6a32d1b"
dependencies = [
"indexmap 2.13.0",
"toml_datetime 1.0.0+spec-1.1.0",
"indexmap 2.13.1",
"toml_datetime 1.1.1+spec-1.1.0",
"toml_parser",
"winnow",
"winnow 1.0.1",
]
[[package]]
name = "toml_parser"
version = "1.0.9+spec-1.1.0"
version = "1.1.2+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4"
checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
dependencies = [
"winnow",
"winnow 1.0.1",
]
[[package]]
......@@ -7972,7 +7903,7 @@ dependencies = [
"http 1.4.0",
"http-body 1.0.1",
"http-body-util",
"hyper 1.8.1",
"hyper 1.9.0",
"hyper-timeout",
"hyper-util",
"percent-encoding",
......@@ -8001,7 +7932,7 @@ dependencies = [
"http 1.4.0",
"http-body 1.0.1",
"http-body-util",
"hyper 1.8.1",
"hyper 1.9.0",
"hyper-timeout",
"hyper-util",
"percent-encoding",
......@@ -8030,7 +7961,7 @@ dependencies = [
"http 1.4.0",
"http-body 1.0.1",
"http-body-util",
"hyper 1.8.1",
"hyper 1.9.0",
"hyper-timeout",
"hyper-util",
"percent-encoding",
......@@ -8127,7 +8058,7 @@ checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
dependencies = [
"futures-core",
"futures-util",
"indexmap 2.13.0",
"indexmap 2.13.1",
"pin-project-lite",
"slab",
"sync_wrapper 1.0.2",
......@@ -8279,19 +8210,6 @@ dependencies = [
"tokio",
]
[[package]]
name = "tungstenite"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13"
dependencies = [
"bytes",
"log",
"rand 0.9.2",
"thiserror 2.0.18",
"utf-8",
]
[[package]]
name = "typeid"
version = "1.0.3"
......@@ -8412,9 +8330,9 @@ dependencies = [
[[package]]
name = "unicode-segmentation"
version = "1.12.0"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c"
[[package]]
name = "unicode-width"
......@@ -8518,12 +8436,6 @@ dependencies = [
"serde_derive",
]
[[package]]
name = "utf-8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "utf16_iter"
version = "1.0.5"
......@@ -8548,7 +8460,7 @@ version = "5.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fcc29c80c21c31608227e0912b2d7fddba57ad76b606890627ba8ee7964e993"
dependencies = [
"indexmap 2.13.0",
"indexmap 2.13.1",
"serde",
"serde_json",
"utoipa-gen",
......@@ -8564,8 +8476,6 @@ dependencies = [
"quote",
"regex",
"syn 2.0.117",
"url",
"uuid",
]
[[package]]
......@@ -8588,9 +8498,9 @@ dependencies = [
[[package]]
name = "uuid"
version = "1.22.0"
version = "1.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37"
checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9"
dependencies = [
"getrandom 0.4.2",
"js-sys",
......@@ -8696,7 +8606,7 @@ dependencies = [
"http 1.4.0",
"http-body 1.0.1",
"http-body-util",
"hyper 1.8.1",
"hyper 1.9.0",
"hyper-util",
"nix 0.30.1",
"parking_lot",
......@@ -8800,9 +8710,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen"
version = "0.2.114"
version = "0.2.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e"
checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0"
dependencies = [
"cfg-if",
"once_cell",
......@@ -8813,23 +8723,19 @@ dependencies = [
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.64"
version = "0.4.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8"
checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e"
dependencies = [
"cfg-if",
"futures-util",
"js-sys",
"once_cell",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.114"
version = "0.2.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6"
checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
......@@ -8837,9 +8743,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.114"
version = "0.2.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3"
checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2"
dependencies = [
"bumpalo",
"proc-macro2",
......@@ -8850,9 +8756,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.114"
version = "0.2.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16"
checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b"
dependencies = [
"unicode-ident",
]
......@@ -8874,7 +8780,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
dependencies = [
"anyhow",
"indexmap 2.13.0",
"indexmap 2.13.1",
"wasm-encoder",
"wasmparser",
]
......@@ -8900,15 +8806,15 @@ checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
dependencies = [
"bitflags 2.11.0",
"hashbrown 0.15.5",
"indexmap 2.13.0",
"indexmap 2.13.1",
"semver",
]
[[package]]
name = "web-sys"
version = "0.3.91"
version = "0.3.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9"
checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a"
dependencies = [
"js-sys",
"wasm-bindgen",
......@@ -9289,6 +9195,15 @@ dependencies = [
"memchr",
]
[[package]]
name = "winnow"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5"
dependencies = [
"memchr",
]
[[package]]
name = "winreg"
version = "0.50.0"
......@@ -9327,7 +9242,7 @@ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
dependencies = [
"anyhow",
"heck 0.5.0",
"indexmap 2.13.0",
"indexmap 2.13.1",
"prettyplease",
"syn 2.0.117",
"wasm-metadata",
......@@ -9358,7 +9273,7 @@ checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
dependencies = [
"anyhow",
"bitflags 2.11.0",
"indexmap 2.13.0",
"indexmap 2.13.1",
"log",
"serde",
"serde_derive",
......@@ -9377,7 +9292,7 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
dependencies = [
"anyhow",
"id-arena",
"indexmap 2.13.0",
"indexmap 2.13.1",
"log",
"semver",
"serde",
......@@ -9395,9 +9310,9 @@ checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936"
[[package]]
name = "writeable"
version = "0.6.2"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
[[package]]
name = "xxhash-rust"
......@@ -9430,9 +9345,9 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
[[package]]
name = "yoke"
version = "0.8.1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954"
checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca"
dependencies = [
"stable_deref_trait",
"yoke-derive",
......@@ -9441,9 +9356,9 @@ dependencies = [
[[package]]
name = "yoke-derive"
version = "0.8.1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
dependencies = [
"proc-macro2",
"quote",
......@@ -9453,18 +9368,18 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.8.42"
version = "0.8.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3"
checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.42"
version = "0.8.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f"
checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
dependencies = [
"proc-macro2",
"quote",
......@@ -9473,18 +9388,18 @@ dependencies = [
[[package]]
name = "zerofrom"
version = "0.1.6"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df"
dependencies = [
"zerofrom-derive",
]
[[package]]
name = "zerofrom-derive"
version = "0.1.6"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
dependencies = [
"proc-macro2",
"quote",
......@@ -9510,9 +9425,9 @@ dependencies = [
[[package]]
name = "zerotrie"
version = "0.2.3"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851"
checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf"
dependencies = [
"displaydoc",
"yoke",
......@@ -9521,9 +9436,9 @@ dependencies = [
[[package]]
name = "zerovec"
version = "0.11.5"
version = "0.11.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002"
checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239"
dependencies = [
"yoke",
"zerofrom",
......@@ -9532,9 +9447,9 @@ dependencies = [
[[package]]
name = "zerovec-derive"
version = "0.11.2"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
dependencies = [
"proc-macro2",
"quote",
......@@ -9552,7 +9467,7 @@ dependencies = [
"crossbeam-utils",
"displaydoc",
"flate2",
"indexmap 2.13.0",
"indexmap 2.13.1",
"memchr",
"thiserror 2.0.18",
"zopfli",
......@@ -9567,7 +9482,7 @@ dependencies = [
"arbitrary",
"crc32fast",
"flate2",
"indexmap 2.13.0",
"indexmap 2.13.1",
"memchr",
"zopfli",
]
......@@ -9635,9 +9550,9 @@ dependencies = [
[[package]]
name = "zune-jpeg"
version = "0.5.13"
version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec5f41c76397b7da451efd19915684f727d7e1d516384ca6bd0ec43ec94de23c"
checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296"
dependencies = [
"zune-core",
]
......@@ -44,7 +44,7 @@ dynamo-tokens = { path = "lib/tokens", version = "1.0.0" }
dynamo-memory = { path = "lib/memory", version = "1.0.0" }
dynamo-mocker = { path = "lib/mocker", version = "1.0.0" }
dynamo-kv-router = { path = "lib/kv-router", version = "1.0.0", features = ["metrics", "runtime-protocols"] }
dynamo-protocols = { path = "lib/protocols", version = "1.0.0", features = ["byot"] }
dynamo-protocols = { path = "lib/protocols", version = "1.0.0" }
dynamo-parsers = { path = "lib/parsers", version = "1.0.0" }
fastokens = { version = "0.1.0" }
......
......@@ -75,7 +75,7 @@ class NvCreateImageRequest(BaseModel):
class ImageData(BaseModel):
"""Individual image data in a response.
Matches the flattened Rust Image enum in lib/async-openai/src/types/image.rs.
Matches the flattened Rust Image enum in lib/protocols/src/types/mod.rs.
"""
url: Optional[str] = None
......
......@@ -244,14 +244,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4288f83726785267c6f2ef073a3d83dc3f9b81464e9f99898240cced85fce35a"
[[package]]
name = "async-openai-macros"
version = "0.1.1"
name = "async-openai"
version = "0.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81872a8e595e8ceceab71c6ba1f9078e313b452a1e31934e6763ef5d308705e4"
checksum = "ec08254d61379df136135d3d1ac04301be7699fd7d9e57655c63ac7d650a6922"
dependencies = [
"proc-macro2",
"quote",
"syn",
"bytes",
"derive_builder",
"getrandom 0.3.4",
"serde",
"serde_json",
]
[[package]]
......@@ -460,20 +462,6 @@ dependencies = [
"tower-service",
]
[[package]]
name = "backoff"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1"
dependencies = [
"futures-core",
"getrandom 0.2.17",
"instant",
"pin-project-lite",
"rand 0.8.5",
"tokio",
]
[[package]]
name = "backon"
version = "1.6.0"
......@@ -1704,26 +1692,14 @@ dependencies = [
name = "dynamo-protocols"
version = "1.0.0"
dependencies = [
"async-openai-macros",
"backoff",
"base64 0.22.1",
"bytes",
"async-openai",
"derive_builder",
"eventsource-stream",
"futures",
"rand 0.9.2",
"reqwest",
"reqwest-eventsource",
"secrecy",
"serde",
"serde_json",
"thiserror 2.0.18",
"tokio",
"tokio-stream",
"tokio-util",
"tracing",
"url",
"utoipa",
"uuid",
]
......@@ -1980,17 +1956,6 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "eventsource-stream"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74fef4569247a5f429d9156b9d0a2599914385dd189c539334c625d8099d90ab"
dependencies = [
"futures-core",
"nom 7.1.3",
"pin-project-lite",
]
[[package]]
name = "exr"
version = "1.74.0"
......@@ -2271,12 +2236,6 @@ version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
[[package]]
name = "futures-timer"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24"
[[package]]
name = "futures-util"
version = "0.3.32"
......@@ -2932,15 +2891,6 @@ dependencies = [
"libc",
]
[[package]]
name = "instant"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
dependencies = [
"cfg-if",
]
[[package]]
name = "interpolate_name"
version = "0.2.4"
......@@ -5533,22 +5483,6 @@ dependencies = [
"webpki-roots 1.0.6",
]
[[package]]
name = "reqwest-eventsource"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "632c55746dbb44275691640e7b40c907c16a2dc1a5842aa98aaec90da6ec6bde"
dependencies = [
"eventsource-stream",
"futures-core",
"futures-timer",
"mime",
"nom 7.1.3",
"pin-project-lite",
"reqwest",
"thiserror 1.0.69",
]
[[package]]
name = "rgb"
version = "0.8.53"
......@@ -5911,7 +5845,6 @@ version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a"
dependencies = [
"serde",
"zeroize",
]
......@@ -7360,8 +7293,6 @@ dependencies = [
"quote",
"regex",
"syn",
"url",
"uuid",
]
[[package]]
......
......@@ -131,9 +131,9 @@ dependencies = [
[[package]]
name = "arc-swap"
version = "1.9.0"
version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a07d1f37ff60921c83bdfc7407723bdefe89b44b98a9b772f225c8f9d67141a6"
checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207"
dependencies = [
"rustversion",
]
......@@ -244,14 +244,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4288f83726785267c6f2ef073a3d83dc3f9b81464e9f99898240cced85fce35a"
[[package]]
name = "async-openai-macros"
version = "0.1.1"
name = "async-openai"
version = "0.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81872a8e595e8ceceab71c6ba1f9078e313b452a1e31934e6763ef5d308705e4"
checksum = "ec08254d61379df136135d3d1ac04301be7699fd7d9e57655c63ac7d650a6922"
dependencies = [
"proc-macro2",
"quote",
"syn",
"bytes",
"derive_builder",
"getrandom 0.3.4",
"serde",
"serde_json",
]
[[package]]
......@@ -460,20 +462,6 @@ dependencies = [
"tower-service",
]
[[package]]
name = "backoff"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1"
dependencies = [
"futures-core",
"getrandom 0.2.17",
"instant",
"pin-project-lite",
"rand 0.8.5",
"tokio",
]
[[package]]
name = "backon"
version = "1.6.0"
......@@ -1719,26 +1707,14 @@ dependencies = [
name = "dynamo-protocols"
version = "1.0.0"
dependencies = [
"async-openai-macros",
"backoff",
"base64 0.22.1",
"bytes",
"async-openai",
"derive_builder",
"eventsource-stream",
"futures",
"rand 0.9.2",
"reqwest",
"reqwest-eventsource",
"secrecy",
"serde",
"serde_json",
"thiserror 2.0.18",
"tokio",
"tokio-stream",
"tokio-util",
"tracing",
"url",
"utoipa",
"uuid",
]
......@@ -2027,17 +2003,6 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "eventsource-stream"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74fef4569247a5f429d9156b9d0a2599914385dd189c539334c625d8099d90ab"
dependencies = [
"futures-core",
"nom 7.1.3",
"pin-project-lite",
]
[[package]]
name = "exr"
version = "1.74.0"
......@@ -2096,11 +2061,11 @@ dependencies = [
[[package]]
name = "fastrand"
version = "2.3.0"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
checksum = "a043dc74da1e37d6afe657061213aa6f425f855399a11d3463c6ecccc4dfda1f"
dependencies = [
"getrandom 0.2.17",
"getrandom 0.3.4",
]
[[package]]
......@@ -2343,12 +2308,6 @@ version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
[[package]]
name = "futures-timer"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24"
[[package]]
name = "futures-util"
version = "0.3.32"
......@@ -3004,15 +2963,6 @@ dependencies = [
"libc",
]
[[package]]
name = "instant"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
dependencies = [
"cfg-if",
]
[[package]]
name = "interpolate_name"
version = "0.2.4"
......@@ -5603,22 +5553,6 @@ dependencies = [
"webpki-roots 1.0.6",
]
[[package]]
name = "reqwest-eventsource"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "632c55746dbb44275691640e7b40c907c16a2dc1a5842aa98aaec90da6ec6bde"
dependencies = [
"eventsource-stream",
"futures-core",
"futures-timer",
"mime",
"nom 7.1.3",
"pin-project-lite",
"reqwest",
"thiserror 1.0.69",
]
[[package]]
name = "rgb"
version = "0.8.53"
......@@ -5981,7 +5915,6 @@ version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a"
dependencies = [
"serde",
"zeroize",
]
......@@ -7430,8 +7363,6 @@ dependencies = [
"quote",
"regex",
"syn",
"url",
"uuid",
]
[[package]]
......
......@@ -182,7 +182,7 @@ pub fn final_response_to_one_chunk_stream(
// Convert FunctionCall to FunctionCallStream if present
#[allow(deprecated)]
let function_call = ch.message.function_call.as_ref().map(|fc| {
dynamo_protocols::types::FunctionCallStream {
dynamo_protocols::types::ChatCompletionStreamResponseDeltaFunctionCall {
name: Some(fc.name.clone()),
arguments: Some(fc.arguments.clone()),
}
......@@ -197,7 +197,7 @@ pub fn final_response_to_one_chunk_stream(
|(i, call)| dynamo_protocols::types::ChatCompletionMessageToolCallChunk {
index: i as u32,
id: Some(call.id.clone()),
r#type: Some(call.r#type.clone()),
r#type: Some(dynamo_protocols::types::FunctionType::Function),
function: Some(dynamo_protocols::types::FunctionCallStream {
name: Some(call.function.name.clone()),
arguments: Some(call.function.arguments.clone()),
......
// SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
pub mod client;
pub mod service;
// SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
//! HTTP clients for streaming LLM responses with performance recording
//!
//! This module provides HTTP clients that leverage async-openai with BYOT (Bring Your Own Types)
//! feature to work with OpenAI-compatible APIs. The clients support recording streaming responses
//! for performance analysis.
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use std::task::{Context, Poll};
use std::time::Instant;
use async_trait::async_trait;
use derive_getters::Dissolve;
use dynamo_protocols::{Client, config::OpenAIConfig, error::OpenAIError};
use futures::Stream;
use serde_json::Value;
use tokio_util::sync::CancellationToken;
use tracing;
use uuid::Uuid;
// Import our existing recording infrastructure
use crate::protocols::Annotated;
use crate::protocols::openai::chat_completions::{
NvCreateChatCompletionRequest, NvCreateChatCompletionStreamResponse,
};
use dynamo_runtime::engine::{
AsyncEngineContext, AsyncEngineContextProvider, AsyncEngineStream, Data, DataStream,
};
/// Configuration for HTTP clients
#[derive(Clone, Default)]
pub struct HttpClientConfig {
/// OpenAI API configuration
pub openai_config: OpenAIConfig,
/// Whether to enable detailed logging
pub verbose: bool,
}
/// Error types for HTTP clients
#[derive(Debug, thiserror::Error)]
pub enum HttpClientError {
#[error("OpenAI API error: {0}")]
OpenAI(#[from] OpenAIError),
#[error("Request timeout")]
Timeout,
#[error("Request cancelled")]
Cancelled,
#[error("Invalid request: {0}")]
InvalidRequest(String),
}
/// Context for HTTP client requests that supports cancellation
/// This bridges AsyncEngineContext and reqwest cancellation
#[derive(Clone)]
pub struct HttpRequestContext {
/// Unique request identifier
id: String,
/// Tokio cancellation token for reqwest integration
cancel_token: CancellationToken,
/// When this context was created
created_at: Instant,
/// Whether the request has been stopped
stopped: Arc<std::sync::atomic::AtomicBool>,
/// Child contexts to be stopped if this is stopped
child_context: Arc<Mutex<Vec<Arc<dyn AsyncEngineContext>>>>,
}
impl HttpRequestContext {
/// Create a new HTTP request context
pub fn new() -> Self {
Self {
id: Uuid::new_v4().to_string(),
cancel_token: CancellationToken::new(),
created_at: Instant::now(),
stopped: Arc::new(std::sync::atomic::AtomicBool::new(false)),
child_context: Arc::new(Mutex::new(Vec::new())),
}
}
/// Create a new context with a specific ID
pub fn with_id(id: String) -> Self {
Self {
id,
cancel_token: CancellationToken::new(),
created_at: Instant::now(),
stopped: Arc::new(std::sync::atomic::AtomicBool::new(false)),
child_context: Arc::new(Mutex::new(Vec::new())),
}
}
/// Create a child context from this parent context
/// The child will be cancelled when the parent is cancelled
pub fn child(&self) -> Self {
Self {
id: Uuid::new_v4().to_string(),
cancel_token: self.cancel_token.child_token(),
created_at: Instant::now(),
stopped: Arc::new(std::sync::atomic::AtomicBool::new(false)),
child_context: Arc::new(Mutex::new(Vec::new())),
}
}
/// Create a child context with a specific ID
pub fn child_with_id(&self, id: String) -> Self {
Self {
id,
cancel_token: self.cancel_token.child_token(),
created_at: Instant::now(),
stopped: Arc::new(std::sync::atomic::AtomicBool::new(false)),
child_context: Arc::new(Mutex::new(Vec::new())),
}
}
/// Get the cancellation token for use with reqwest
pub fn cancellation_token(&self) -> CancellationToken {
self.cancel_token.clone()
}
/// Get the elapsed time since context creation
pub fn elapsed(&self) -> std::time::Duration {
self.created_at.elapsed()
}
}
impl Default for HttpRequestContext {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for HttpRequestContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("HttpRequestContext")
.field("id", &self.id)
.field("created_at", &self.created_at)
.field("is_stopped", &self.is_stopped())
.field("is_killed", &self.is_killed())
.field("is_cancelled", &self.cancel_token.is_cancelled())
.finish()
}
}
#[async_trait]
impl AsyncEngineContext for HttpRequestContext {
fn id(&self) -> &str {
&self.id
}
fn stop(&self) {
// Clone child Arcs to avoid deadlock if parent is accidentally linked under child
let children = self
.child_context
.lock()
.expect("Failed to lock child context")
.iter()
.cloned()
.collect::<Vec<_>>();
for child in children {
child.stop();
}
self.stopped
.store(true, std::sync::atomic::Ordering::Release);
self.cancel_token.cancel();
}
fn stop_generating(&self) {
// Clone child Arcs to avoid deadlock if parent is accidentally linked under child
let children = self
.child_context
.lock()
.expect("Failed to lock child context")
.iter()
.cloned()
.collect::<Vec<_>>();
for child in children {
child.stop_generating();
}
// For HTTP clients, stop_generating is the same as stop
self.stopped
.store(true, std::sync::atomic::Ordering::Release);
self.cancel_token.cancel();
}
fn kill(&self) {
// Clone child Arcs to avoid deadlock if parent is accidentally linked under child
let children = self
.child_context
.lock()
.expect("Failed to lock child context")
.iter()
.cloned()
.collect::<Vec<_>>();
for child in children {
child.kill();
}
self.stopped
.store(true, std::sync::atomic::Ordering::Release);
self.cancel_token.cancel();
}
fn is_stopped(&self) -> bool {
self.stopped.load(std::sync::atomic::Ordering::Acquire)
}
fn is_killed(&self) -> bool {
self.stopped.load(std::sync::atomic::Ordering::Acquire)
}
async fn stopped(&self) {
self.cancel_token.cancelled().await;
}
async fn killed(&self) {
// For HTTP clients, killed is the same as stopped
self.cancel_token.cancelled().await;
}
fn link_child(&self, child: Arc<dyn AsyncEngineContext>) {
self.child_context
.lock()
.expect("Failed to lock child context")
.push(child);
}
}
/// Base HTTP client with common functionality
pub struct BaseHttpClient {
/// async-openai client
client: Client<OpenAIConfig>,
/// Client configuration
config: HttpClientConfig,
/// Root context for this client
root_context: HttpRequestContext,
}
impl BaseHttpClient {
/// Create a new base HTTP client
pub fn new(config: HttpClientConfig) -> Self {
let client = Client::with_config(config.openai_config.clone());
Self {
client,
config,
root_context: HttpRequestContext::new(),
}
}
/// Get a reference to the underlying async-openai client
pub fn client(&self) -> &Client<OpenAIConfig> {
&self.client
}
/// Create a new request context as a child of the root context
pub fn create_context(&self) -> HttpRequestContext {
self.root_context.child()
}
/// Create a new request context with a specific ID as a child of the root context
pub fn create_context_with_id(&self, id: String) -> HttpRequestContext {
self.root_context.child_with_id(id)
}
/// Get the root context for this client
pub fn root_context(&self) -> &HttpRequestContext {
&self.root_context
}
/// Check if verbose logging is enabled
pub fn is_verbose(&self) -> bool {
self.config.verbose
}
}
/// Type alias for NV chat response stream
pub type NvChatResponseStream =
DataStream<Result<Annotated<NvCreateChatCompletionStreamResponse>, OpenAIError>>;
/// Type alias for generic BYOT response stream
pub type ByotResponseStream = DataStream<Result<Value, OpenAIError>>;
/// Type alias for pure OpenAI chat response stream
pub type OpenAIChatResponseStream =
DataStream<Result<dynamo_protocols::types::CreateChatCompletionStreamResponse, OpenAIError>>;
/// A wrapped HTTP response stream that combines a stream with its context
/// This provides a unified interface for HTTP client responses
#[derive(Dissolve)]
pub struct HttpResponseStream<T> {
/// The underlying stream of responses
pub stream: DataStream<T>,
/// The context for this request
pub context: Arc<dyn AsyncEngineContext>,
}
impl<T> HttpResponseStream<T> {
/// Create a new HttpResponseStream
pub fn new(stream: DataStream<T>, context: Arc<dyn AsyncEngineContext>) -> Self {
Self { stream, context }
}
}
impl<T: Data> Stream for HttpResponseStream<T> {
type Item = T;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
Pin::new(&mut self.stream).poll_next(cx)
}
}
impl<T: Data> AsyncEngineContextProvider for HttpResponseStream<T> {
fn context(&self) -> Arc<dyn AsyncEngineContext> {
self.context.clone()
}
}
impl<T: Data> HttpResponseStream<T> {
/// Convert this HttpResponseStream to a Pin<Box<dyn AsyncEngineStream<T>>>
/// This requires the stream to be Send + Sync, which may not be true for all streams
pub fn into_async_engine_stream(self) -> Pin<Box<dyn AsyncEngineStream<T>>>
where
T: 'static,
{
// This will only work if the underlying stream is actually Send + Sync
// For now, we create a wrapper that assumes this
Box::pin(AsyncEngineStreamWrapper {
stream: self.stream,
context: self.context,
})
}
}
/// A wrapper that implements AsyncEngineStream for streams that are Send + Sync
struct AsyncEngineStreamWrapper<T> {
stream: DataStream<T>,
context: Arc<dyn AsyncEngineContext>,
}
impl<T: Data> Stream for AsyncEngineStreamWrapper<T> {
type Item = T;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
Pin::new(&mut self.stream).poll_next(cx)
}
}
impl<T: Data> AsyncEngineContextProvider for AsyncEngineStreamWrapper<T> {
fn context(&self) -> Arc<dyn AsyncEngineContext> {
self.context.clone()
}
}
impl<T: Data> AsyncEngineStream<T> for AsyncEngineStreamWrapper<T> {}
impl<T> std::fmt::Debug for AsyncEngineStreamWrapper<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AsyncEngineStreamWrapper")
.field("context", &self.context)
.finish()
}
}
impl<T: Data> std::fmt::Debug for HttpResponseStream<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("HttpResponseStream")
.field("context", &self.context)
.finish()
}
}
/// Type alias for HttpResponseStream with NV chat completion responses
pub type NvHttpResponseStream =
HttpResponseStream<Result<Annotated<NvCreateChatCompletionStreamResponse>, OpenAIError>>;
/// Type alias for HttpResponseStream with BYOT responses
pub type ByotHttpResponseStream = HttpResponseStream<Result<Value, OpenAIError>>;
/// Type alias for HttpResponseStream with pure OpenAI responses
pub type OpenAIHttpResponseStream = HttpResponseStream<
Result<dynamo_protocols::types::CreateChatCompletionStreamResponse, OpenAIError>,
>;
/// Pure OpenAI client using standard async-openai types
pub struct PureOpenAIClient {
base: BaseHttpClient,
}
impl PureOpenAIClient {
/// Create a new pure OpenAI client
pub fn new(config: HttpClientConfig) -> Self {
Self {
base: BaseHttpClient::new(config),
}
}
/// Create streaming chat completions using standard OpenAI types
/// Uses a client-managed context
pub async fn chat_stream(
&self,
request: dynamo_protocols::types::CreateChatCompletionRequest,
) -> Result<OpenAIHttpResponseStream, HttpClientError> {
let ctx = self.base.create_context();
self.chat_stream_with_context(request, ctx).await
}
/// Create streaming chat completions with a custom context
pub async fn chat_stream_with_context(
&self,
request: dynamo_protocols::types::CreateChatCompletionRequest,
context: HttpRequestContext,
) -> Result<OpenAIHttpResponseStream, HttpClientError> {
let ctx_arc: Arc<dyn AsyncEngineContext> = Arc::new(context.clone());
if !request.stream.unwrap_or(false) {
return Err(HttpClientError::InvalidRequest(
"chat_stream requires the request to have 'stream': true".to_string(),
));
}
if self.base.is_verbose() {
tracing::info!(
"Starting pure OpenAI chat stream for request {}",
context.id()
);
}
// Create the stream with cancellation support
let stream = self
.base
.client()
.chat()
.create_stream(request)
.await
.map_err(HttpClientError::OpenAI)?;
// TODO: In Phase 3, we'll add cancellation integration with reqwest
// For now, return the stream as-is
Ok(HttpResponseStream::new(stream, ctx_arc))
}
}
/// NV Custom client using NvCreateChatCompletionRequest with Annotated responses
pub struct NvCustomClient {
base: BaseHttpClient,
}
impl NvCustomClient {
/// Create a new NV custom client
pub fn new(config: HttpClientConfig) -> Self {
Self {
base: BaseHttpClient::new(config),
}
}
/// Create streaming chat completions using NV custom types
/// Uses a client-managed context
pub async fn chat_stream(
&self,
request: NvCreateChatCompletionRequest,
) -> Result<NvHttpResponseStream, HttpClientError> {
let ctx = self.base.create_context();
self.chat_stream_with_context(request, ctx).await
}
/// Create streaming chat completions with a custom context
pub async fn chat_stream_with_context(
&self,
request: NvCreateChatCompletionRequest,
context: HttpRequestContext,
) -> Result<NvHttpResponseStream, HttpClientError> {
let ctx_arc: Arc<dyn AsyncEngineContext> = Arc::new(context.clone());
if !request.inner.stream.unwrap_or(false) {
return Err(HttpClientError::InvalidRequest(
"chat_stream requires the request to have 'stream': true".to_string(),
));
}
if self.base.is_verbose() {
tracing::info!(
"Starting NV custom chat stream for request {}",
context.id()
);
}
// Use BYOT feature to send NvCreateChatCompletionRequest
// The stream type is explicitly specified to deserialize directly into Annotated<NvCreateChatCompletionStreamResponse>
let stream = self
.base
.client()
.chat()
.create_stream_byot(request)
.await
.map_err(HttpClientError::OpenAI)?;
Ok(HttpResponseStream::new(stream, ctx_arc))
}
}
/// Generic BYOT client using serde_json::Value for maximum flexibility
pub struct GenericBYOTClient {
base: BaseHttpClient,
}
impl GenericBYOTClient {
/// Create a new generic BYOT client
pub fn new(config: HttpClientConfig) -> Self {
Self {
base: BaseHttpClient::new(config),
}
}
/// Create streaming chat completions using arbitrary JSON values
/// Uses a client-managed context
pub async fn chat_stream(
&self,
request: Value,
) -> Result<ByotHttpResponseStream, HttpClientError> {
let ctx = self.base.create_context();
self.chat_stream_with_context(request, ctx).await
}
/// Create streaming chat completions with a custom context
pub async fn chat_stream_with_context(
&self,
request: Value,
context: HttpRequestContext,
) -> Result<ByotHttpResponseStream, HttpClientError> {
let ctx_arc: Arc<dyn AsyncEngineContext> = Arc::new(context.clone());
if self.base.is_verbose() {
tracing::info!(
"Starting generic BYOT chat stream for request {}",
context.id()
);
}
// Validate that the request has stream: true
if let Some(stream_val) = request.get("stream") {
if !stream_val.as_bool().unwrap_or(false) {
return Err(HttpClientError::InvalidRequest(
"Request must have 'stream': true for streaming".to_string(),
));
}
} else {
return Err(HttpClientError::InvalidRequest(
"Request must include 'stream' field".to_string(),
));
}
// Use BYOT feature with raw JSON
// The stream type is explicitly specified to deserialize directly into serde_json::Value
let stream = self
.base
.client()
.chat()
.create_stream_byot(request)
.await
.map_err(HttpClientError::OpenAI)?;
Ok(HttpResponseStream::new(stream, ctx_arc))
}
}
// TODO: Implement recording integration in Phase 3:
// - Recording wrapper functions
// - Capacity hints from request parameters
// - Integration with existing recording infrastructure
#[cfg(test)]
mod tests {
use super::*;
use tokio::time::{Duration, sleep};
#[tokio::test]
async fn test_http_request_context_creation() {
let ctx = HttpRequestContext::new();
assert!(!ctx.id().is_empty());
assert!(!ctx.is_stopped());
assert!(!ctx.is_killed());
}
#[tokio::test]
async fn test_http_request_context_child() {
let parent = HttpRequestContext::new();
let child = parent.child();
// Child should have different ID
assert_ne!(parent.id(), child.id());
// Child should not be stopped initially
assert!(!child.is_stopped());
// When parent is stopped, child should be cancelled via token
parent.stop();
assert!(parent.is_stopped());
assert!(child.cancellation_token().is_cancelled());
}
#[tokio::test]
async fn test_http_request_context_child_with_id() {
let parent = HttpRequestContext::new();
let child_id = "test-child";
let child = parent.child_with_id(child_id.to_string());
assert_eq!(child.id(), child_id);
assert!(!child.is_stopped());
// Test hierarchical cancellation
parent.stop();
assert!(child.cancellation_token().is_cancelled());
}
#[tokio::test]
async fn test_http_request_context_cancellation() {
let ctx = HttpRequestContext::new();
let cancel_token = ctx.cancellation_token();
// Test stop functionality
assert!(!ctx.is_stopped());
ctx.stop();
assert!(ctx.is_stopped());
assert!(cancel_token.is_cancelled());
}
#[tokio::test]
async fn test_http_request_context_kill() {
let ctx = HttpRequestContext::new();
// Test kill functionality
assert!(!ctx.is_killed());
ctx.kill();
assert!(ctx.is_killed());
assert!(ctx.is_stopped());
}
#[tokio::test]
async fn test_http_request_context_async_cancellation() {
let ctx = HttpRequestContext::new();
// Test async cancellation
let ctx_clone = ctx.clone();
let task = tokio::spawn(async move {
ctx_clone.stopped().await;
});
// Give a moment for the task to start waiting
sleep(Duration::from_millis(10)).await;
// Cancel the context
ctx.stop();
// The task should complete
task.await.unwrap();
}
#[test]
fn test_base_http_client_creation() {
let config = HttpClientConfig::default();
let client = BaseHttpClient::new(config);
assert!(!client.is_verbose());
// Test that client has a root context
assert!(!client.root_context().id().is_empty());
}
#[test]
fn test_base_http_client_context_creation() {
let config = HttpClientConfig::default();
let client = BaseHttpClient::new(config);
// Test creating child contexts
let ctx1 = client.create_context();
let ctx2 = client.create_context();
// Should have different IDs
assert_ne!(ctx1.id(), ctx2.id());
// Should be children of root context
client.root_context().stop();
assert!(ctx1.cancellation_token().is_cancelled());
assert!(ctx2.cancellation_token().is_cancelled());
}
#[test]
fn test_base_http_client_context_with_id() {
let config = HttpClientConfig::default();
let client = BaseHttpClient::new(config);
let custom_id = "custom-request-id";
let ctx = client.create_context_with_id(custom_id.to_string());
assert_eq!(ctx.id(), custom_id);
// Should still be child of root
client.root_context().stop();
assert!(ctx.cancellation_token().is_cancelled());
}
#[test]
fn test_http_client_config_defaults() {
let config = HttpClientConfig::default();
assert!(!config.verbose);
}
#[test]
fn test_pure_openai_client_creation() {
let config = HttpClientConfig::default();
let _client = PureOpenAIClient::new(config);
// If we get here, creation succeeded
}
#[test]
fn test_nv_custom_client_creation() {
let config = HttpClientConfig::default();
let _client = NvCustomClient::new(config);
// If we get here, creation succeeded
}
#[test]
fn test_generic_byot_client_creation() {
let config = HttpClientConfig::default();
let _client = GenericBYOTClient::new(config);
// If we get here, creation succeeded
}
}
......@@ -1544,6 +1544,7 @@ async fn responses(
temperature: request.inner.temperature,
top_p: request.inner.top_p,
max_output_tokens: request.inner.max_output_tokens,
parallel_tool_calls: request.inner.parallel_tool_calls,
store: request.inner.store,
tools: request.inner.tools.clone(),
tool_choice: request.inner.tool_choice.clone(),
......@@ -1788,11 +1789,6 @@ pub fn validate_response_unsupported_fields(
VALIDATION_PREFIX.to_string() + "`prompt` is not supported.",
));
}
if inner.store == Some(true) {
return Some(ErrorMessage::not_implemented_error(
VALIDATION_PREFIX.to_string() + "`store: true` is not supported.",
));
}
None
}
......@@ -1965,6 +1961,9 @@ async fn images(
.map(|m| match m {
dynamo_protocols::types::ImageModel::DallE2 => "dall-e-2".to_string(),
dynamo_protocols::types::ImageModel::DallE3 => "dall-e-3".to_string(),
dynamo_protocols::types::ImageModel::GptImage1 => "gpt-image-1".to_string(),
dynamo_protocols::types::ImageModel::GptImage1dot5 => "gpt-image-1.5".to_string(),
dynamo_protocols::types::ImageModel::GptImage1Mini => "gpt-image-1-mini".to_string(),
dynamo_protocols::types::ImageModel::Other(s) => s.clone(),
})
.unwrap_or_else(|| "diffusion".to_string());
......@@ -2540,6 +2539,17 @@ mod tests {
assert!(result.is_none(), "parallel_tool_calls should be supported");
}
#[test]
fn test_validate_unsupported_fields_accepts_store() {
let mut request = make_base_request();
request.inner.store = Some(true);
let result = validate_response_unsupported_fields(&request);
assert!(
result.is_none(),
"store should be supported for audit opt-in"
);
}
#[test]
fn test_validate_unsupported_fields_detects_flags() {
#[allow(clippy::type_complexity)]
......@@ -2559,7 +2569,6 @@ mod tests {
})
}),
),
("store", Box::new(|r| r.store = Some(true))),
];
for (field, set_field) in unsupported_cases {
......@@ -3290,8 +3299,7 @@ mod tests {
use dynamo_protocols::types::{
ChatChoiceStream, ChatCompletionMessageToolCallChunk, ChatCompletionStreamResponseDelta,
ChatCompletionToolType, CreateChatCompletionStreamResponse, FinishReason,
FunctionCallStream,
CreateChatCompletionStreamResponse, FinishReason, FunctionCallStream, FunctionType,
};
use dynamo_runtime::protocols::annotated::Annotated;
......@@ -3444,7 +3452,7 @@ mod tests {
let tool_call = ChatCompletionMessageToolCallChunk {
index: 0,
id: id.map(|s| s.to_string()),
r#type: Some(ChatCompletionToolType::Function),
r#type: Some(FunctionType::Function),
function: Some(FunctionCallStream {
name: name.map(|s| s.to_string()),
arguments: arguments.map(|s| s.to_string()),
......@@ -3537,7 +3545,7 @@ mod tests {
let tc1 = ChatCompletionMessageToolCallChunk {
index: 0,
id: Some("call_1".to_string()),
r#type: Some(ChatCompletionToolType::Function),
r#type: Some(FunctionType::Function),
function: Some(FunctionCallStream {
name: Some("get_weather".to_string()),
arguments: Some(r#"{"city":"Paris"}"#.to_string()),
......@@ -3546,7 +3554,7 @@ mod tests {
let tc2 = ChatCompletionMessageToolCallChunk {
index: 1,
id: Some("call_2".to_string()),
r#type: Some(ChatCompletionToolType::Function),
r#type: Some(FunctionType::Function),
function: Some(FunctionCallStream {
name: Some("get_time".to_string()),
arguments: Some(r#"{"tz":"UTC"}"#.to_string()),
......@@ -3609,7 +3617,7 @@ mod tests {
let complete = ChatCompletionMessageToolCallChunk {
index: 0,
id: Some("call_complete".to_string()),
r#type: Some(ChatCompletionToolType::Function),
r#type: Some(FunctionType::Function),
function: Some(FunctionCallStream {
name: Some("get_weather".to_string()),
arguments: Some(r#"{"city":"Paris"}"#.to_string()),
......@@ -3618,7 +3626,7 @@ mod tests {
let incomplete = ChatCompletionMessageToolCallChunk {
index: 1,
id: Some("call_partial".to_string()),
r#type: Some(ChatCompletionToolType::Function),
r#type: Some(FunctionType::Function),
function: Some(FunctionCallStream {
name: Some("search".to_string()),
arguments: None, // still streaming
......@@ -3658,7 +3666,7 @@ mod tests {
let tool_call = ChatCompletionMessageToolCallChunk {
index: 0,
id: Some("call_999".to_string()),
r#type: Some(ChatCompletionToolType::Function),
r#type: Some(FunctionType::Function),
function: None,
};
#[allow(deprecated)]
......
......@@ -947,7 +947,6 @@ mod tests {
fn create_mock_response_with_logprobs(
token_logprobs: Vec<ChatCompletionTokenLogprob>,
) -> NvCreateChatCompletionStreamResponse {
#[expect(deprecated)]
NvCreateChatCompletionStreamResponse {
inner: dynamo_protocols::types::CreateChatCompletionStreamResponse {
id: "test_id".to_string(),
......@@ -984,7 +983,6 @@ mod tests {
fn create_mock_response_with_multiple_choices(
choices_logprobs: Vec<Vec<ChatCompletionTokenLogprob>>,
) -> NvCreateChatCompletionStreamResponse {
#[expect(deprecated)]
let choices = choices_logprobs
.into_iter()
.enumerate()
......@@ -1339,7 +1337,6 @@ mod tests {
#[test]
fn test_logprob_extractor_with_missing_data() {
// Test with choice that has no logprobs
#[expect(deprecated)]
let response = NvCreateChatCompletionStreamResponse {
inner: dynamo_protocols::types::CreateChatCompletionStreamResponse {
id: "test_id".to_string(),
......
......@@ -732,7 +732,7 @@ mod tests {
use super::*;
use dynamo_protocols::types::{
ChatChoiceStream, ChatCompletionMessageContent, ChatCompletionMessageToolCallChunk,
ChatCompletionStreamResponseDelta, ChatCompletionToolType, FunctionCallStream,
ChatCompletionStreamResponseDelta, FunctionCallStream, FunctionType,
};
fn text_chunk(text: &str) -> NvCreateChatCompletionStreamResponse {
......@@ -783,7 +783,7 @@ mod tests {
tool_calls: Some(vec![ChatCompletionMessageToolCallChunk {
index: tc_index,
id: id.map(String::from),
r#type: Some(ChatCompletionToolType::Function),
r#type: Some(FunctionType::Function),
function: Some(FunctionCallStream {
name: name.map(String::from),
arguments: args.map(String::from),
......
......@@ -20,7 +20,7 @@ use dynamo_protocols::types::{
ChatCompletionRequestToolMessageContent, ChatCompletionRequestUserMessage,
ChatCompletionRequestUserMessageContent, ChatCompletionRequestUserMessageContentPart,
ChatCompletionTool, ChatCompletionToolChoiceOption, ChatCompletionToolType, FunctionName,
FunctionObject, ImageUrl, ReasoningContent,
FunctionObject, FunctionType, ImageUrl, ReasoningContent,
};
use uuid::Uuid;
......@@ -312,7 +312,7 @@ fn convert_assistant_blocks(
segments.push(std::mem::take(&mut pending_reasoning));
tool_calls.push(ChatCompletionMessageToolCall {
id: id.clone(),
r#type: ChatCompletionToolType::Function,
r#type: FunctionType::Function,
function: dynamo_protocols::types::FunctionCall {
name: name.clone(),
arguments: serde_json::to_string(input).unwrap_or_default(),
......
......@@ -35,6 +35,7 @@ pub use delta::DeltaGenerator;
#[derive(ToSchema, Serialize, Deserialize, Validate, Debug, Clone)]
pub struct NvCreateChatCompletionRequest {
#[serde(flatten)]
#[schema(value_type = Object)]
pub inner: dynamo_protocols::types::CreateChatCompletionRequest,
#[serde(flatten, default)]
......
......@@ -4,6 +4,8 @@
use futures::{Stream, StreamExt};
use std::collections::HashMap;
use dynamo_parsers::tool_calling::try_tool_call_parse_aggregate;
use super::{NvCreateChatCompletionResponse, NvCreateChatCompletionStreamResponse};
use crate::protocols::{
Annotated,
......@@ -75,11 +77,11 @@ fn convert_tool_chunk_to_message_tool_call(
chunk: &dynamo_protocols::types::ChatCompletionMessageToolCallChunk,
) -> Option<dynamo_protocols::types::ChatCompletionMessageToolCall> {
// Convert ChatCompletionMessageToolCallChunk to ChatCompletionMessageToolCall
if let (Some(id), Some(r#type), Some(function)) = (&chunk.id, &chunk.r#type, &chunk.function) {
if let (Some(id), Some(function)) = (&chunk.id, &chunk.function) {
if let (Some(name), Some(arguments)) = (&function.name, &function.arguments) {
Some(dynamo_protocols::types::ChatCompletionMessageToolCall {
id: id.clone(),
r#type: r#type.clone(),
r#type: dynamo_protocols::types::FunctionType::Function,
function: dynamo_protocols::types::FunctionCall {
name: name.clone(),
arguments: arguments.clone(),
......@@ -120,9 +122,9 @@ impl DeltaAggregator {
/// * `Err(String)` if an error occurs during processing.
pub async fn apply(
stream: impl Stream<Item = Annotated<NvCreateChatCompletionStreamResponse>>,
_parsing_options: ParsingOptions,
parsing_options: ParsingOptions,
) -> Result<NvCreateChatCompletionResponse, String> {
let aggregator = stream
let mut aggregator = stream
.fold(DeltaAggregator::new(), |mut aggregator, delta| async move {
// Attempt to unwrap the delta, capturing any errors.
let delta = match delta.ok() {
......@@ -256,6 +258,37 @@ impl DeltaAggregator {
return Err(error);
}
if let Some(parser) = parsing_options.tool_call_parser.as_deref() {
for choice in aggregator.choices.values_mut() {
if choice
.tool_calls
.as_ref()
.is_some_and(|calls| !calls.is_empty())
|| choice.text.is_empty()
{
continue;
}
let (tool_calls, content) =
match try_tool_call_parse_aggregate(&choice.text, Some(parser), None).await {
Ok(result) => result,
Err(error) => {
tracing::debug!(
error = %error,
parser,
"failed to parse aggregated chat tool calls"
);
continue;
}
};
if !tool_calls.is_empty() {
choice.tool_calls = Some(tool_calls);
choice.text = content.unwrap_or_default();
}
}
}
// Extract aggregated choices and sort them by index.
let mut choices: Vec<_> = aggregator
.choices
......@@ -405,7 +438,7 @@ mod tests {
dynamo_protocols::types::ChatCompletionMessageToolCallChunk {
index: 0,
id: Some("test_id".to_string()),
r#type: Some(dynamo_protocols::types::ChatCompletionToolType::Function),
r#type: Some(dynamo_protocols::types::FunctionType::Function),
function: Some(dynamo_protocols::types::FunctionCallStream {
name: tool_calls["name"].as_str().map(|s| s.to_string()),
arguments: Some(serde_json::to_string(&tool_calls["arguments"]).unwrap()),
......@@ -788,6 +821,10 @@ mod tests {
assert!(choice.message.tool_calls.is_some());
let tool_calls = choice.message.tool_calls.as_ref().unwrap();
assert_eq!(tool_calls.len(), 1);
assert_eq!(
tool_calls[0].r#type,
dynamo_protocols::types::FunctionType::Function
);
// Most importantly, verify that finish reason was overridden to ToolCalls despite original being Stop
assert_eq!(
......@@ -831,6 +868,10 @@ mod tests {
assert!(choice.message.tool_calls.is_some());
let tool_calls = choice.message.tool_calls.as_ref().unwrap();
assert_eq!(tool_calls.len(), 1);
assert_eq!(
tool_calls[0].r#type,
dynamo_protocols::types::FunctionType::Function
);
// Verify that finish reason was overridden to ToolCalls despite original being Length
assert_eq!(
......@@ -1073,4 +1114,75 @@ mod tests {
assert_eq!(tool_calls.len(), 1);
assert_eq!(tool_calls[0].function.name, "get_weather");
}
#[tokio::test]
async fn test_parses_aggregated_tool_call_text_into_tool_calls() {
let annotated_delta = create_test_delta(
0,
"<tool_call>\n{\"name\":\"get_weather\",\"arguments\":{\"location\":\"SF\"}}\n</tool_call>",
Some(dynamo_protocols::types::Role::Assistant),
Some(dynamo_protocols::types::FinishReason::Stop),
None,
None,
);
let stream = Box::pin(stream::iter(vec![annotated_delta]));
let result = DeltaAggregator::apply(
stream,
ParsingOptions::new(Some("hermes".to_string()), None),
)
.await;
assert!(result.is_ok());
let response = result.unwrap();
let choice = &response.inner.choices[0];
assert_eq!(
choice.finish_reason,
Some(dynamo_protocols::types::FinishReason::ToolCalls)
);
assert_eq!(choice.message.content, None);
let tool_calls = choice.message.tool_calls.as_ref().unwrap();
assert_eq!(tool_calls.len(), 1);
assert_eq!(
tool_calls[0].r#type,
dynamo_protocols::types::FunctionType::Function
);
assert_eq!(tool_calls[0].function.name, "get_weather");
assert_eq!(tool_calls[0].function.arguments, "{\"location\":\"SF\"}");
}
#[tokio::test]
async fn test_preserves_non_tool_content_when_parsing_aggregated_tool_calls() {
let annotated_delta = create_test_delta(
0,
"hello\n<tool_call>\n{\"name\":\"get_weather\",\"arguments\":{\"location\":\"SF\"}}\n</tool_call>",
Some(dynamo_protocols::types::Role::Assistant),
Some(dynamo_protocols::types::FinishReason::Stop),
None,
None,
);
let stream = Box::pin(stream::iter(vec![annotated_delta]));
let result = DeltaAggregator::apply(
stream,
ParsingOptions::new(Some("hermes".to_string()), None),
)
.await;
assert!(result.is_ok());
let response = result.unwrap();
let choice = &response.inner.choices[0];
assert_eq!(
choice.message.content,
Some(ChatCompletionMessageContent::Text("hello".to_string()))
);
assert_eq!(
choice.finish_reason,
Some(dynamo_protocols::types::FinishReason::ToolCalls)
);
assert_eq!(
choice.message.tool_calls.as_ref().unwrap()[0].r#type,
dynamo_protocols::types::FunctionType::Function
);
}
}
......@@ -4,7 +4,7 @@
use async_stream::stream;
use dynamo_protocols::types::{
ChatChoiceLogprobs, ChatChoiceStream, ChatCompletionMessageToolCallChunk,
ChatCompletionStreamResponseDelta, FinishReason, FunctionCallStream, Role,
ChatCompletionStreamResponseDelta, FinishReason, FunctionCallStream, FunctionType, Role,
};
use dynamo_parsers::tool_calling::parsers::get_tool_parser_map;
......@@ -902,7 +902,7 @@ impl JailedStream {
.map(|(idx, tool_call)| ChatCompletionMessageToolCallChunk {
index: (tool_call_offset + idx) as u32,
id: Some(tool_call.id),
r#type: Some(tool_call.r#type),
r#type: Some(FunctionType::Function),
function: Some(FunctionCallStream {
name: Some(tool_call.function.name),
arguments: Some(tool_call.function.arguments),
......@@ -971,7 +971,7 @@ impl JailedStream {
ChatCompletionMessageToolCallChunk {
index,
id: Some(format!("call-{}", Uuid::new_v4())),
r#type: Some(dynamo_protocols::types::ChatCompletionToolType::Function),
r#type: Some(FunctionType::Function),
function: Some(FunctionCallStream {
name: Some(name),
arguments: Some(arguments),
......
......@@ -27,6 +27,7 @@ pub use delta::DeltaGenerator;
#[derive(ToSchema, Serialize, Deserialize, Validate, Debug, Clone)]
pub struct NvCreateCompletionRequest {
#[serde(flatten)]
#[schema(value_type = Object)]
pub inner: dynamo_protocols::types::CreateCompletionRequest,
#[serde(flatten)]
......@@ -47,6 +48,7 @@ pub struct NvCreateCompletionRequest {
#[derive(ToSchema, Serialize, Deserialize, Validate, Debug, Clone)]
pub struct NvCreateCompletionResponse {
#[serde(flatten)]
#[schema(value_type = Object)]
pub inner: dynamo_protocols::types::CreateCompletionResponse,
#[serde(skip_serializing_if = "Option::is_none")]
pub nvext: Option<serde_json::Value>,
......
......@@ -15,6 +15,7 @@ pub use nvext::{NvExt, NvExtProvider};
#[derive(ToSchema, Serialize, Deserialize, Validate, Debug, Clone)]
pub struct NvCreateEmbeddingRequest {
#[serde(flatten)]
#[schema(value_type = Object)]
pub inner: dynamo_protocols::types::CreateEmbeddingRequest,
#[serde(skip_serializing_if = "Option::is_none")]
......@@ -30,6 +31,7 @@ pub struct NvCreateEmbeddingRequest {
#[derive(ToSchema, Serialize, Deserialize, Validate, Debug, Clone)]
pub struct NvCreateEmbeddingResponse {
#[serde(flatten)]
#[schema(value_type = Object)]
pub inner: dynamo_protocols::types::CreateEmbeddingResponse,
}
......
......@@ -42,6 +42,11 @@ impl NvImagesResponse {
inner: dynamo_protocols::types::ImagesResponse {
created: 0,
data: vec![],
background: None,
output_format: None,
quality: None,
size: None,
usage: None,
},
}
}
......
......@@ -3,26 +3,27 @@
pub mod stream_converter;
use std::collections::HashMap;
use dynamo_protocols::types::responses::{
AssistantRole, FunctionCallOutput, FunctionToolCall, IncludeEnum, InputContent, InputItem,
InputParam, InputRole, InputTokenDetails, Instructions, Item, MessageItem, OutputItem,
OutputMessage, OutputMessageContent, OutputStatus, OutputTextContent, OutputTokenDetails,
Reasoning, ReasoningItem, Response, ResponseTextParam, ResponseUsage, Role as ResponseRole,
ServiceTier, Status, Summary, SummaryPart, TextResponseFormatConfiguration, Tool,
ServiceTier, Status, SummaryPart, SummaryTextContent, TextResponseFormatConfiguration, Tool,
ToolChoiceOptions, ToolChoiceParam, Truncation,
};
use dynamo_protocols::types::{
ChatCompletionMessageToolCall, ChatCompletionNamedToolChoice,
ChatCompletionRequestAssistantMessage, ChatCompletionRequestAssistantMessageContent,
ChatCompletionRequestMessage, ChatCompletionRequestMessageContentPartImage,
ChatCompletionRequestMessageContentPartText, ChatCompletionRequestMessageContentPartVideo,
ChatCompletionRequestSystemMessage, ChatCompletionRequestSystemMessageContent,
ChatCompletionRequestToolMessage, ChatCompletionRequestToolMessageContent,
ChatCompletionRequestUserMessage, ChatCompletionRequestUserMessageContent,
ChatCompletionRequestUserMessageContentPart, ChatCompletionTool,
ChatCompletionToolChoiceOption, ChatCompletionToolType, CreateChatCompletionRequest,
FunctionName, FunctionObject, ImageDetail as ChatImageDetail, ImageUrl, ResponseFormat,
ServiceTier as ChatServiceTier, VideoUrl,
ChatCompletionRequestMessageContentPartText, ChatCompletionRequestSystemMessage,
ChatCompletionRequestSystemMessageContent, ChatCompletionRequestToolMessage,
ChatCompletionRequestToolMessageContent, ChatCompletionRequestUserMessage,
ChatCompletionRequestUserMessageContent, ChatCompletionRequestUserMessageContentPart,
ChatCompletionTool, ChatCompletionToolChoiceOption, ChatCompletionToolType,
CreateChatCompletionRequest, FunctionName, FunctionObject, FunctionType,
ImageDetail as ChatImageDetail, ImageUrl, ResponseFormat, ServiceTier as ChatServiceTier,
};
use dynamo_runtime::protocols::annotated::AnnotationsProvider;
use serde::{Deserialize, Serialize};
......@@ -38,6 +39,7 @@ use super::{OpenAISamplingOptionsProvider, OpenAIStopConditionsProvider};
pub struct NvCreateResponse {
/// Flattened CreateResponse fields (model, input, temperature, etc.)
#[serde(flatten)]
#[schema(value_type = Object)]
pub inner: dynamo_protocols::types::responses::CreateResponse,
#[serde(skip_serializing_if = "Option::is_none")]
......@@ -46,8 +48,9 @@ pub struct NvCreateResponse {
#[derive(ToSchema, Serialize, Deserialize, Validate, Debug, Clone)]
pub struct NvResponse {
/// Flattened Response fields.
/// Flattened Response fields (includes upstream + extended spec fields).
#[serde(flatten)]
#[schema(value_type = Object)]
pub inner: dynamo_protocols::types::responses::Response,
/// NVIDIA extension field for response metadata (worker IDs, etc.)
......@@ -143,13 +146,18 @@ impl OpenAIStopConditionsProvider for NvCreateResponse {
// ---------------------------------------------------------------------------
/// Convert a Responses API ImageDetail to the Chat Completions ImageDetail.
fn convert_image_detail(
detail: &dynamo_protocols::types::responses::ImageDetail,
) -> ChatImageDetail {
match detail {
dynamo_protocols::types::responses::ImageDetail::Auto => ChatImageDetail::Auto,
dynamo_protocols::types::responses::ImageDetail::Low => ChatImageDetail::Low,
dynamo_protocols::types::responses::ImageDetail::High => ChatImageDetail::High,
/// The responses module re-exports an `ImageDetail` from the upstream async-openai
/// crate which is distinct from `dynamo_protocols::types::ImageDetail` (chat).
/// We bridge via serde to avoid direct cross-crate type dependencies.
fn convert_image_detail_str(detail: &impl serde::Serialize) -> ChatImageDetail {
match serde_json::to_value(detail)
.ok()
.and_then(|v| v.as_str().map(String::from))
.as_deref()
{
Some("low") => ChatImageDetail::Low,
Some("high") => ChatImageDetail::High,
_ => ChatImageDetail::Auto,
}
}
......@@ -177,52 +185,31 @@ fn convert_input_content_to_user_content(
));
}
InputContent::InputImage(img) => {
let url_str = img.image_url.as_deref().unwrap_or_default();
if img.file_id.is_some() && img.image_url.is_none() {
return Err(anyhow::anyhow!(
"Image input by file_id is not yet supported"
));
}
let url_str = img
.image_url
.as_deref()
.ok_or_else(|| anyhow::anyhow!("input_image requires image_url"))?;
let url = url::Url::parse(url_str)
.map_err(|e| anyhow::anyhow!("Invalid image URL '{}': {}", url_str, e))?;
chat_parts.push(ChatCompletionRequestUserMessageContentPart::ImageUrl(
ChatCompletionRequestMessageContentPartImage {
image_url: ImageUrl {
url,
detail: Some(convert_image_detail(&img.detail)),
detail: Some(convert_image_detail_str(&img.detail)),
uuid: None,
},
},
));
}
InputContent::InputVideo(vid) => {
let url = url::Url::parse(&vid.video)
.map_err(|e| anyhow::anyhow!("Invalid video URL '{}': {}", vid.video, e))?;
chat_parts.push(ChatCompletionRequestUserMessageContentPart::VideoUrl(
ChatCompletionRequestMessageContentPartVideo {
video_url: VideoUrl {
url,
detail: None,
uuid: None,
},
},
));
}
InputContent::InputAudio(_) => {
return Err(anyhow::anyhow!("Audio input content is not yet supported"));
}
// TODO: handle InputVideo / InputAudio when upstream adds them
InputContent::InputFile(_) => {
return Err(anyhow::anyhow!("File input content is not yet supported"));
}
InputContent::OutputText(t) => {
chat_parts.push(ChatCompletionRequestUserMessageContentPart::Text(
ChatCompletionRequestMessageContentPartText {
text: t.text.clone(),
},
));
}
InputContent::Refusal(r) => {
chat_parts.push(ChatCompletionRequestUserMessageContentPart::Text(
ChatCompletionRequestMessageContentPartText {
text: r.refusal.clone(),
},
));
}
}
}
Ok(ChatCompletionRequestUserMessageContent::Array(chat_parts))
......@@ -234,8 +221,6 @@ fn convert_input_content_to_text(content: &[InputContent]) -> String {
.iter()
.filter_map(|p| match p {
InputContent::InputText(t) => Some(t.text.as_str()),
InputContent::OutputText(t) => Some(t.text.as_str()),
InputContent::Refusal(r) => Some(r.refusal.as_str()),
_ => None,
})
.collect::<Vec<_>>()
......@@ -315,7 +300,7 @@ fn convert_input_items_to_messages(
audio: None,
tool_calls: Some(vec![ChatCompletionMessageToolCall {
id: fc.call_id.clone(),
r#type: ChatCompletionToolType::Function,
r#type: FunctionType::Function,
function: dynamo_protocols::types::FunctionCall {
name: fc.name.clone(),
arguments: fc.arguments.clone(),
......@@ -528,8 +513,13 @@ impl TryFrom<NvCreateResponse> for NvCreateChatCompletionRequest {
temperature: resp.inner.temperature,
top_p: resp.inner.top_p,
max_completion_tokens: resp.inner.max_output_tokens,
store: resp.inner.store,
parallel_tool_calls: resp.inner.parallel_tool_calls,
top_logprobs,
metadata: resp.inner.metadata,
metadata: resp
.inner
.metadata
.map(|m| serde_json::to_value(m).unwrap_or_default()),
stream,
tools,
tool_choice,
......@@ -634,6 +624,7 @@ pub struct ResponseParams {
pub temperature: Option<f32>,
pub top_p: Option<f32>,
pub max_output_tokens: Option<u32>,
pub parallel_tool_calls: Option<bool>,
pub store: Option<bool>,
pub tools: Option<Vec<Tool>>,
pub tool_choice: Option<ToolChoiceParam>,
......@@ -668,9 +659,10 @@ pub(super) fn normalize_tools(tools: Vec<Tool>) -> Vec<Tool> {
/// Build an assistant text message output item.
fn make_text_message(id: String, text: String) -> OutputItem {
OutputItem::Message(OutputMessage {
id: Some(id),
id,
role: AssistantRole::Assistant,
status: Some(OutputStatus::Completed),
status: OutputStatus::Completed,
phase: None,
content: vec![OutputMessageContent::OutputText(OutputTextContent {
text,
annotations: vec![],
......@@ -684,6 +676,7 @@ fn make_function_call(name: String, arguments: String) -> OutputItem {
OutputItem::FunctionCall(FunctionToolCall {
arguments,
call_id: format!("call_{}", Uuid::new_v4().simple()),
namespace: None,
name,
id: Some(format!("fc_{}", Uuid::new_v4().simple())),
status: Some(OutputStatus::Completed),
......@@ -712,6 +705,7 @@ pub fn chat_completion_to_response(
output.push(OutputItem::FunctionCall(FunctionToolCall {
arguments: tc.function.arguments.clone(),
call_id: tc.id.clone(),
namespace: None,
name: tc.function.name.clone(),
id: Some(format!("fc_{}", Uuid::new_v4().simple())),
status: Some(OutputStatus::Completed),
......@@ -725,7 +719,7 @@ pub fn chat_completion_to_response(
{
output.push(OutputItem::Reasoning(ReasoningItem {
id: format!("rs_{}", Uuid::new_v4().simple()),
summary: vec![SummaryPart::SummaryText(Summary {
summary: vec![SummaryPart::SummaryText(SummaryTextContent {
text: reasoning_text,
})],
content: None,
......@@ -807,16 +801,8 @@ pub fn chat_completion_to_response(
output,
// Spec-required defaults (OpenResponses requires these as non-null)
background: Some(false),
frequency_penalty: Some(0.0),
metadata: Some(serde_json::Value::Object(Default::default())),
parallel_tool_calls: Some(true),
presence_penalty: Some(0.0),
// Echo actual request values, falling back to spec defaults.
// store: false because this branch does not persist responses.
store: api_context
.map(|ctx| ctx.store)
.or(params.store)
.or(Some(false)),
metadata: Some(HashMap::new()),
parallel_tool_calls: params.parallel_tool_calls.or(Some(true)),
temperature: params.temperature.or(Some(1.0)),
text: Some(params.text.clone().unwrap_or(ResponseTextParam {
format: TextResponseFormatConfiguration::Text,
......@@ -842,7 +828,6 @@ pub fn chat_completion_to_response(
incomplete_details: None,
instructions: params.instructions.clone().map(Instructions::Text),
max_output_tokens: params.max_output_tokens,
max_tool_calls: None,
previous_response_id: api_context.and_then(|ctx| ctx.previous_response_id.clone()),
prompt: None,
prompt_cache_key: None,
......@@ -880,8 +865,8 @@ pub fn chat_completion_to_response(
mod tests {
use dynamo_protocols::types::responses::{
CreateResponse, FunctionCallOutput, FunctionCallOutputItemParam, FunctionTool,
FunctionToolCall, ImageDetail, InputContent, InputImageContent, InputItem, InputMessage,
InputParam, InputRole, InputTextContent, Item, MessageItem, Tool,
FunctionToolCall, InputContent, InputImageContent, InputItem, InputMessage, InputParam,
InputRole, InputTextContent, Item, MessageItem, Tool,
};
use dynamo_protocols::types::{
ChatCompletionRequestMessage, ChatCompletionRequestUserMessageContent,
......@@ -962,6 +947,15 @@ mod tests {
}
}
#[test]
fn test_store_mapped_to_chat_completion_request() {
let mut req = make_response_with_input("audit me");
req.inner.store = Some(true);
let nv_req: NvCreateChatCompletionRequest = req.try_into().unwrap();
assert_eq!(nv_req.inner.store, Some(true));
}
#[test]
fn test_instructions_prepended_as_system_message() {
let req = NvCreateResponse {
......@@ -1009,9 +1003,10 @@ mod tests {
status: None,
}))),
InputItem::Item(Item::Message(MessageItem::Output(OutputMessage {
id: Some("msg_1".into()),
id: "msg_1".into(),
role: AssistantRole::Assistant,
status: Some(OutputStatus::Completed),
status: OutputStatus::Completed,
phase: None,
content: vec![OutputMessageContent::OutputText(OutputTextContent {
text: "4".into(),
annotations: vec![],
......@@ -1058,7 +1053,7 @@ mod tests {
text: "What is in this image?".into(),
}),
InputContent::InputImage(InputImageContent {
detail: ImageDetail::Auto,
detail: Default::default(), // ImageDetail::Auto
file_id: None,
image_url: Some("https://example.com/cat.jpg".into()),
}),
......@@ -1102,6 +1097,7 @@ mod tests {
InputItem::Item(Item::FunctionCall(FunctionToolCall {
arguments: r#"{"location":"SF"}"#.into(),
call_id: "call_123".into(),
namespace: None,
name: "get_weather".into(),
id: None,
status: None,
......@@ -1147,6 +1143,7 @@ mod tests {
})),
strict: Some(true),
description: Some("Get weather info".into()),
defer_loading: None,
})]),
..Default::default()
},
......@@ -1230,7 +1227,7 @@ mod tests {
refusal: None,
tool_calls: Some(vec![ChatCompletionMessageToolCall {
id: "call_abc".into(),
r#type: ChatCompletionToolType::Function,
r#type: FunctionType::Function,
function: dynamo_protocols::types::FunctionCall {
name: "get_weather".into(),
arguments: r#"{"location":"SF"}"#.into(),
......@@ -1424,6 +1421,15 @@ thinking
assert_eq!(chat.inner.service_tier, Some(ChatServiceTier::Priority));
}
#[test]
fn test_parallel_tool_calls_mapped_to_chat_completion() {
let mut req = make_response_with_input("parallel tools off");
req.inner.parallel_tool_calls = Some(false);
let chat: NvCreateChatCompletionRequest = req.try_into().unwrap();
assert_eq!(chat.inner.parallel_tool_calls, Some(false));
}
#[test]
fn test_response_echoes_reasoning() {
use dynamo_protocols::types::ReasoningEffort;
......@@ -1517,25 +1523,48 @@ thinking
}
#[test]
fn test_output_message_deserializes_without_id_and_status() {
use dynamo_protocols::types::responses::{InputItem, Item, MessageItem};
fn test_response_echoes_parallel_tool_calls() {
let params = ResponseParams {
parallel_tool_calls: Some(false),
..Default::default()
};
let chat_resp = NvCreateChatCompletionResponse {
inner: dynamo_protocols::types::CreateChatCompletionResponse {
choices: vec![],
created: 0,
id: "test".into(),
model: "m".into(),
service_tier: None,
system_fingerprint: None,
object: "chat.completion".into(),
usage: None,
},
nvext: None,
};
let resp = chat_completion_to_response(chat_resp, &params, None).unwrap();
assert_eq!(resp.inner.parallel_tool_calls, Some(false));
}
#[test]
fn test_output_message_without_id_and_status_fails_to_deserialize() {
use dynamo_protocols::types::responses::InputItem;
// With the upstream schema, `id` (String) and `status` (OutputStatus) are
// required on OutputMessage. An assistant message without them can't
// deserialize as either OutputMessage or InputMessage (wrong role).
let json = serde_json::json!({
"role": "assistant",
"content": [{"type": "output_text", "text": "Hello!", "annotations": []}],
"type": "message"
});
let item: InputItem = serde_json::from_value(json).unwrap();
match item {
InputItem::Item(Item::Message(MessageItem::Output(msg))) => {
assert_eq!(msg.role, AssistantRole::Assistant);
assert_eq!(msg.content.len(), 1);
assert!(msg.id.is_none());
assert_eq!(msg.status, None);
}
other => panic!("Expected Item::Message(Output), got {:?}", other),
}
let result = serde_json::from_value::<InputItem>(json);
assert!(
result.is_err(),
"Expected deserialization to fail without id and status"
);
}
#[test]
......@@ -1553,8 +1582,8 @@ thinking
let item: InputItem = serde_json::from_value(json).unwrap();
match item {
InputItem::Item(Item::Message(MessageItem::Output(msg))) => {
assert_eq!(msg.id.as_deref(), Some("msg_abc123"));
assert_eq!(msg.status, Some(OutputStatus::Completed));
assert_eq!(msg.id, "msg_abc123");
assert_eq!(msg.status, OutputStatus::Completed);
}
other => panic!("Expected Item::Message(Output), got {:?}", other),
}
......@@ -1664,4 +1693,42 @@ thinking
let resp = chat_completion_to_response(chat_resp, &params, None).unwrap();
assert_eq!(resp.inner.truncation, Some(Truncation::Disabled));
}
/// Validate the JSON wire shape of NvResponse.
///
/// The migration to upstream async-openai v0.34 removed fields that were
/// incorrectly present on our old local Response type (they belong on the
/// request, not the response, per the OpenAI Responses API spec).
#[test]
fn test_response_wire_format_shape() {
let chat_resp = make_chat_resp_with_text("hello");
let params = ResponseParams::default();
let resp = chat_completion_to_response(chat_resp, &params, None).unwrap();
let json = serde_json::to_value(&resp).unwrap();
// Fields that were on our old local type but are NOT in the OpenAI
// Responses API spec -- they are request-level, not response-level.
assert!(json.get("frequency_penalty").is_none());
assert!(json.get("presence_penalty").is_none());
assert!(json.get("store").is_none());
assert!(json.get("max_tool_calls").is_none());
// Fields that should be present with expected values
assert_eq!(json["object"], "response");
assert_eq!(json["status"], "completed");
assert_eq!(json["metadata"], serde_json::json!({}));
assert!(json["output"].is_array());
assert!(json["output"][0].get("id").is_some());
assert!(json["output"][0].get("status").is_some());
// Optional fields with None should be omitted (upstream uses skip_serializing_if)
assert!(json.get("error").is_none());
assert!(json.get("incomplete_details").is_none());
assert!(json.get("billing").is_none());
assert!(json.get("conversation").is_none());
assert!(json.get("safety_identifier").is_none());
// nvext should be omitted when None
assert!(json.get("nvext").is_none());
}
}
......@@ -9,6 +9,7 @@
//! `response.output_text.done` -> `response.content_part.done` ->
//! `response.output_item.done` -> `response.completed` -> `[DONE]`
use std::collections::HashMap;
use std::time::{SystemTime, UNIX_EPOCH};
use axum::response::sse::Event;
......@@ -121,17 +122,8 @@ impl ResponseStreamConverter {
output,
// Echo request params with spec-required defaults for omitted fields
background: Some(false),
frequency_penalty: Some(0.0),
metadata: Some(serde_json::Value::Object(Default::default())),
parallel_tool_calls: Some(true),
presence_penalty: Some(0.0),
// store: false because this branch does not persist responses.
store: self
.api_context
.as_ref()
.map(|ctx| ctx.store)
.or(self.params.store)
.or(Some(false)),
metadata: Some(HashMap::new()),
parallel_tool_calls: self.params.parallel_tool_calls.or(Some(true)),
temperature: self.params.temperature.or(Some(1.0)),
text: Some(self.params.text.clone().unwrap_or(ResponseTextParam {
format: TextResponseFormatConfiguration::Text,
......@@ -158,7 +150,6 @@ impl ResponseStreamConverter {
incomplete_details: None,
instructions: self.params.instructions.clone().map(Instructions::Text),
max_output_tokens: self.params.max_output_tokens,
max_tool_calls: None,
previous_response_id: self
.api_context
.as_ref()
......@@ -250,10 +241,11 @@ impl ResponseStreamConverter {
sequence_number: self.next_seq(),
output_index,
item: OutputItem::Message(OutputMessage {
id: Some(self.message_item_id.clone()),
id: self.message_item_id.clone(),
content: vec![],
role: AssistantRole::Assistant,
status: Some(OutputStatus::InProgress),
phase: None,
status: OutputStatus::InProgress,
}),
},
);
......@@ -333,6 +325,7 @@ impl ResponseStreamConverter {
item: OutputItem::FunctionCall(FunctionToolCall {
id: Some(item_id),
call_id,
namespace: None,
name: fc_name,
arguments: String::new(),
status: Some(OutputStatus::InProgress),
......@@ -398,6 +391,7 @@ impl ResponseStreamConverter {
item: OutputItem::FunctionCall(FunctionToolCall {
id: Some(fc_item_id),
call_id: fc_call_id,
namespace: None,
name: fc_name,
arguments: fc_args,
status: Some(OutputStatus::Completed),
......@@ -450,14 +444,15 @@ impl ResponseStreamConverter {
sequence_number: self.next_seq(),
output_index: self.message_output_index,
item: OutputItem::Message(OutputMessage {
id: Some(self.message_item_id.clone()),
id: self.message_item_id.clone(),
content: vec![OutputMessageContent::OutputText(OutputTextContent {
text: self.accumulated_text.clone(),
annotations: vec![],
logprobs: Some(vec![]),
})],
role: AssistantRole::Assistant,
status: Some(OutputStatus::Completed),
phase: None,
status: OutputStatus::Completed,
}),
});
events.push(make_sse_event(&item_done));
......@@ -497,6 +492,7 @@ impl ResponseStreamConverter {
item: OutputItem::FunctionCall(FunctionToolCall {
id: Some(item_id),
call_id,
namespace: None,
name: fc_name,
arguments: accumulated_args,
status: Some(OutputStatus::Completed),
......@@ -509,14 +505,15 @@ impl ResponseStreamConverter {
let mut output = Vec::new();
if self.message_started {
output.push(OutputItem::Message(OutputMessage {
id: Some(self.message_item_id.clone()),
id: self.message_item_id.clone(),
content: vec![OutputMessageContent::OutputText(OutputTextContent {
text: self.accumulated_text.clone(),
annotations: vec![],
logprobs: Some(vec![]),
})],
role: AssistantRole::Assistant,
status: Some(OutputStatus::Completed),
phase: None,
status: OutputStatus::Completed,
}));
}
for fc in &self.function_call_items {
......@@ -524,6 +521,7 @@ impl ResponseStreamConverter {
output.push(OutputItem::FunctionCall(FunctionToolCall {
id: Some(fc.item_id.clone()),
call_id: fc.call_id.clone(),
namespace: None,
name: fc.name.clone(),
arguments: fc.accumulated_args.clone(),
status: Some(OutputStatus::Completed),
......@@ -675,7 +673,7 @@ mod tests {
use crate::protocols::unified::ResponsesContext;
use dynamo_protocols::types::{
ChatChoiceStream, ChatCompletionMessageContent, ChatCompletionMessageToolCallChunk,
ChatCompletionStreamResponseDelta, ChatCompletionToolType, FunctionCallStream,
ChatCompletionStreamResponseDelta, FunctionCallStream, FunctionType,
};
fn default_params() -> ResponseParams {
......@@ -684,6 +682,7 @@ mod tests {
temperature: None,
top_p: None,
max_output_tokens: None,
parallel_tool_calls: None,
store: None,
tools: None,
tool_choice: None,
......@@ -714,7 +713,7 @@ mod tests {
tool_calls: Some(vec![ChatCompletionMessageToolCallChunk {
index: tc_index,
id: id.map(String::from),
r#type: Some(ChatCompletionToolType::Function),
r#type: Some(FunctionType::Function),
function: Some(FunctionCallStream {
name: name.map(String::from),
arguments: args.map(String::from),
......@@ -932,7 +931,7 @@ mod tests {
);
}
/// Verify that `with_context` populates `previous_response_id` and `store`
/// Verify that `with_context` populates `previous_response_id`
/// in the generated Response objects.
#[test]
fn test_with_context_enriches_response() {
......@@ -949,16 +948,14 @@ mod tests {
let _ = conv.process_chunk(&text_chunk("Hello"));
let _end_events = conv.emit_end_events();
// Verify the Response object carries the context values through
let response = conv.make_response(Status::Completed, vec![]);
assert_eq!(
response.previous_response_id.as_deref(),
Some("resp_prev_123")
);
assert_eq!(response.store, Some(true));
}
/// Without context, previous_response_id is None and store defaults to false.
/// Without context, previous_response_id is None.
#[test]
fn test_without_context_defaults() {
let params = ResponseParams::default();
......@@ -966,6 +963,17 @@ mod tests {
let response = conv.make_response(Status::Completed, vec![]);
assert_eq!(response.previous_response_id, None);
assert_eq!(response.store, Some(false));
}
#[test]
fn test_stream_response_echoes_parallel_tool_calls() {
let params = ResponseParams {
parallel_tool_calls: Some(false),
..Default::default()
};
let conv = ResponseStreamConverter::new("test-model".into(), params);
let response = conv.make_response(Status::Completed, vec![]);
assert_eq!(response.parallel_tool_calls, Some(false));
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment