litellm/litellm-rust/crates/host-native/src/driver.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

99 lines
3.4 KiB
Rust

use std::ops::ControlFlow;
use litellm_host::{
interceptors::Interceptors,
machine::{HostFailure, Machine, MachineStep},
protocol::{HostRequest, InterceptRequest, Protocol, Reply, StreamDelivery},
};
use crate::services::HostCallHandler;
type ProtocolOf<M> = <M as Machine>::Protocol;
type ErrorOf<M> = <ProtocolOf<M> as Protocol>::Error;
pub enum Boundary<M: Machine> {
Complete(M::Complete),
Open(<ProtocolOf<M> as Protocol>::StreamHead),
Chunk(<ProtocolOf<M> as Protocol>::Chunk),
}
/// Answers host calls and interceptors inline and stops at each stream delivery, holding its demand
/// reply until the consumer advances again. Dropping the driver drops the in-flight call.
pub struct Driver<M: Machine, S, H> {
machine: M,
services: S,
interceptors: H,
demand: Option<Reply<ControlFlow<()>>>,
}
impl<M, S, H> Driver<M, S, H>
where
M: Machine,
S: HostCallHandler<ProtocolOf<M>>,
H: Interceptors<ErrorOf<M>>,
{
pub fn new(machine: M, services: S, interceptors: H) -> Self {
Self {
machine,
services,
interceptors,
demand: None,
}
}
pub async fn advance(&mut self) -> Result<Boundary<M>, ErrorOf<M>> {
self.resume(ControlFlow::Continue(())).await
}
pub async fn detach(&mut self) -> Result<Boundary<M>, ErrorOf<M>> {
self.resume(ControlFlow::Break(())).await
}
/// Interrupts the machine with a failure the consumer hit at the last stream boundary,
/// dropping the held demand reply unanswered
pub async fn fail(&mut self, error: ErrorOf<M>) -> Result<M::Complete, ErrorOf<M>> {
self.demand = None;
self.machine.interrupt(HostFailure::Error(error)).await
}
async fn resume(&mut self, demand: ControlFlow<()>) -> Result<Boundary<M>, ErrorOf<M>> {
if let Some(reply) = self.demand.take() {
reply.send(demand);
}
loop {
let request = match self.machine.resume().await? {
MachineStep::Complete(complete) => return Ok(Boundary::Complete(complete)),
MachineStep::Suspended(request) => request,
};
let answered = match request {
HostRequest::HostCall(call) => self.services.handle_host_call(call).await,
HostRequest::Intercept(InterceptRequest::BeforeProviderRequest {
wire,
context,
reply,
}) => self
.interceptors
.before_provider_request(*wire, *context)
.await
.map(|wire| reply.send(wire)),
HostRequest::Intercept(InterceptRequest::AfterProviderResponse { raw, reply }) => {
self.interceptors
.after_provider_response(raw)
.await
.map(|()| reply.send(()))
}
HostRequest::Stream(StreamDelivery::Open(head, reply)) => {
self.demand = Some(reply);
return Ok(Boundary::Open(head));
}
HostRequest::Stream(StreamDelivery::Chunk(chunk, reply)) => {
self.demand = Some(reply);
return Ok(Boundary::Chunk(chunk));
}
};
if let Err(error) = answered {
return self.fail(error).await.map(Boundary::Complete);
}
}
}
}