litellm/litellm-rust/crates/host/src/call.rs
devin-ai-integration[bot] e4190d86a6
refactor(rust): centralize host execution and compose callbacks (#43515)
* refactor(rust): extract litellm-host-native as the shared Rust host driver

Move service and hook dispatch out of host-http into a Driver that owns the
machine and Rust handlers, returning at completion or a stream boundary and
holding the demand reply until the consumer advances. Move the in-process
runner onto the same driver. host-http now layers encoding, SSE, body polling
and lifecycle observation over it. host-python keeps driving litellm-host
directly

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(rust): interrupt the machine when the in-process stream consumer fails

Restores the pre-refactor interruption path for StreamConsumer errors via
Driver::fail and ports the generic run lifecycle tests into host-native.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* refactor(rust): separate the machine contract from coroutine execution

* auth update

* refactor(rust): use standard flow control for host requests

* style(rust): keep host driver imports formatted

* chores

* mostly relocation

* refactor(rust): separate interceptors from queued observers

* refactor(rust): centralize legacy callback mappings and lifecycle

* docs: define Python host boundaries and migration plan

* refactor: enforce Python host and bridge boundaries

* refactor(rust): separate operations from callback composition

* refactor(rust): compose SDK policy through call hooks

---------

Co-authored-by: Yujong Lee <yujong@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-28 19:20:27 +00:00

77 lines
2.2 KiB
Rust

use crate::observation::ObservationSender;
use std::future::Future;
use futures_util::{TryStreamExt, stream::BoxStream};
use crate::{
machine::{CallMachine, ChannelInterceptors, HostServices, MachineFault},
protocol::Protocol,
};
pub enum CallOutput<Response, Head, Chunk, Error> {
Complete(Response),
Stream {
head: Head,
chunks: BoxStream<'static, Result<Chunk, Error>>,
},
}
#[derive(Debug, PartialEq, Eq)]
pub enum HostedCompletion<Response> {
Complete(Response),
StreamEnded,
Detached,
}
impl<Response> From<Response> for HostedCompletion<Response> {
fn from(response: Response) -> Self {
Self::Complete(response)
}
}
pub type OutputOf<P> = CallOutput<
<P as Protocol>::Response,
<P as Protocol>::StreamHead,
<P as Protocol>::Chunk,
<P as Protocol>::Error,
>;
pub type HostedMachine<P> = CallMachine<P, HostedCompletion<<P as Protocol>::Response>>;
pub fn hosted_call<P, F, Fut>(
request: P::Request,
observers: Option<ObservationSender>,
execute: F,
) -> HostedMachine<P>
where
P: Protocol,
P::Error: From<MachineFault>,
F: FnOnce(
P::Request,
HostServices<P>,
ChannelInterceptors<P>,
Option<ObservationSender>,
) -> Fut
+ Send
+ 'static,
Fut: Future<Output = Result<OutputOf<P>, P::Error>> + Send + 'static,
{
CallMachine::new(observers, move |host| {
Box::pin(async move {
match execute(request, host.services, host.interceptors, host.observers).await? {
CallOutput::Complete(response) => Ok(HostedCompletion::Complete(response)),
CallOutput::Stream { head, mut chunks } => {
if host.stream.open_stream(head).await?.is_break() {
return Ok(HostedCompletion::Detached);
}
while let Some(chunk) = chunks.try_next().await? {
if host.stream.send_chunk(chunk).await?.is_break() {
return Ok(HostedCompletion::Detached);
}
}
Ok(HostedCompletion::StreamEnded)
}
}
})
})
}