1use std::sync::Arc;
9
10use async_stream::stream;
11use either::Either;
12use tokio::sync::mpsc;
13use tracing::debug;
14
15use super::compaction::maybe_compact_context;
16use super::gateway::{
17 GatewayCallResult, LoopDecision, append_gateway_calls_to_new_input, append_output_items_to_input,
18 append_tool_outputs, classify_round, complete_gateway_event_plans, emit_gateway_completed_events,
19 emit_gateway_start_events, execute_and_emit_output_calls, execute_output_calls, gateway_event_plans,
20 has_client_owned_calls, is_client_custom_call, is_gateway_owned_call, public_output_items,
21};
22use super::gateway_accumulator::{GatewayStreamAccumulator, StreamEvent, error_sse_chunk};
23use crate::events::EventFrame;
24use crate::executor::error::ExecutorResult;
25use crate::executor::inference::DONE_MARKER;
26use crate::executor::persist::persist_if_needed;
27use crate::executor::rehydrate::rehydrate_conversation;
28use crate::executor::request::{ExecutionContext, RequestContext};
29use crate::executor::upstream::{emit_deferred_stream_events, fetch_blocking_payload, fetch_stream_payload};
30use crate::tool::{ToolRegistry, mcp};
31use crate::types::io::{OutputItem, ResponseUsage, ToolChoice};
32use crate::types::request_response::{IncompleteDetails, RequestPayload, ResponsePayload};
33
34pub use crate::executor::inference::BoxStream;
35
36const MAX_GATEWAY_TOOL_ROUNDS: usize = 10;
37
38fn add_usage(total: ResponseUsage, usage: ResponseUsage) -> ResponseUsage {
39 ResponseUsage {
40 input_tokens: total.input_tokens.saturating_add(usage.input_tokens),
41 output_tokens: total.output_tokens.saturating_add(usage.output_tokens),
42 total_tokens: total.total_tokens.saturating_add(usage.total_tokens),
43 input_tokens_details: crate::types::io::InputTokenDetails {
44 cached_tokens: total
45 .input_tokens_details
46 .cached_tokens
47 .saturating_add(usage.input_tokens_details.cached_tokens),
48 },
49 output_tokens_details: crate::types::io::OutputTokenDetails {
50 reasoning_tokens: total
51 .output_tokens_details
52 .reasoning_tokens
53 .saturating_add(usage.output_tokens_details.reasoning_tokens),
54 },
55 }
56}
57
58fn accumulate_usage(total: &mut Option<ResponseUsage>, usage: Option<ResponseUsage>) {
59 if let Some(usage) = usage {
60 *total = Some(total.map_or(usage, |current| add_usage(current, usage)));
61 }
62}
63
64struct AbortOnDrop<T> {
65 handle: tokio::task::JoinHandle<T>,
66}
67
68impl<T> AbortOnDrop<T> {
69 fn new(handle: tokio::task::JoinHandle<T>) -> Self {
70 Self { handle }
71 }
72}
73
74impl<T> std::ops::Deref for AbortOnDrop<T> {
75 type Target = tokio::task::JoinHandle<T>;
76
77 fn deref(&self) -> &Self::Target {
78 &self.handle
79 }
80}
81
82impl<T> std::ops::DerefMut for AbortOnDrop<T> {
83 fn deref_mut(&mut self) -> &mut Self::Target {
84 &mut self.handle
85 }
86}
87
88impl<T> Drop for AbortOnDrop<T> {
89 fn drop(&mut self) {
90 if !self.handle.is_finished() {
91 self.handle.abort();
92 }
93 }
94}
95
96async fn run_until_gateway_tools_complete(
97 mut ctx: RequestContext,
98 exec_ctx: &ExecutionContext,
99 auth: Option<&str>,
100 stream_upstream: bool,
101 mut stream: Option<(&mut GatewayStreamAccumulator, &mpsc::UnboundedSender<StreamEvent>)>,
102) -> ExecutorResult<(ResponsePayload, RequestContext)> {
103 let mut executors = exec_ctx.gateway_executors.request_scoped();
104 let registry: ToolRegistry = match ctx.enriched_request.tools.as_mut() {
105 Some(tools) => ToolRegistry::build_with_handlers(tools, &mut executors).await?,
106 None => ToolRegistry::default(),
107 };
108 let mut combined_output: Vec<OutputItem> = registry
109 .mcp_list_tools_items()
110 .iter()
111 .map(mcp::handler::list_tools_output_item)
112 .collect();
113 let mut combined_usage = None;
114
115 for round in 0..MAX_GATEWAY_TOOL_ROUNDS {
116 let compaction_usage = maybe_compact_context(&mut ctx, exec_ctx, auth).await?;
117 accumulate_usage(&mut combined_usage, compaction_usage);
118 let output_offset = combined_output.len();
119 let (mut payload, deferred_stream_events): (ResponsePayload, Vec<_>) = if stream_upstream {
120 let stream_payload = fetch_stream_payload(
121 &ctx,
122 exec_ctx,
123 auth,
124 ®istry,
125 stream
126 .as_mut()
127 .map(|(accumulator, sender)| (&mut **accumulator, *sender)),
128 output_offset,
129 )
130 .await?;
131 (stream_payload.payload, stream_payload.deferred_events)
132 } else {
133 (fetch_blocking_payload(&ctx, exec_ctx, auth).await?, Vec::new())
134 };
135 registry.restore_final_payload_output(&mut payload.output);
136 accumulate_usage(&mut combined_usage, payload.usage.take());
137 let current_output = std::mem::take(&mut payload.output);
138 for item in ¤t_output {
139 if let OutputItem::CustomToolCall(call) = item {
140 debug!(
141 response_id = %ctx.response_id,
142 call_id = %call.call_id,
143 name = %call.name,
144 input_bytes = call.input.len(),
145 "custom tool call requires client execution"
146 );
147 }
148 }
149 let has_client_owned = has_client_owned_calls(¤t_output, ®istry);
150 let gateway_results = execute_and_emit_round_output_calls(
151 ¤t_output,
152 ®istry,
153 output_offset,
154 deferred_stream_events,
155 &ctx,
156 stream
157 .as_mut()
158 .map(|(accumulator, sender)| (&mut **accumulator, *sender)),
159 )
160 .await?;
161 let public_output = public_output_items(¤t_output, ®istry, &gateway_results);
162 combined_output.extend(public_output);
163
164 match classify_round(has_client_owned, &gateway_results, round, MAX_GATEWAY_TOOL_ROUNDS) {
165 LoopDecision::RequiresClientAction => {
169 append_gateway_calls_to_new_input(&mut ctx, ¤t_output, ®istry);
170 append_tool_outputs(
171 &mut ctx,
172 gateway_results.into_iter().map(|result| result.input_item).collect(),
173 );
174 finalize_loop(&mut payload, combined_output, combined_usage, &ctx);
175 return Ok((payload, ctx));
176 }
177 LoopDecision::Done => {
179 finalize_loop(&mut payload, combined_output, combined_usage, &ctx);
180 return Ok((payload, ctx));
181 }
182 LoopDecision::Incomplete(reason) => {
188 append_gateway_calls_to_new_input(&mut ctx, ¤t_output, ®istry);
189 append_tool_outputs(
190 &mut ctx,
191 gateway_results.into_iter().map(|result| result.input_item).collect(),
192 );
193 finalize_loop(&mut payload, combined_output, combined_usage, &ctx);
194 "incomplete".clone_into(&mut payload.status);
195 payload.incomplete_details = Some(IncompleteDetails { reason: Some(reason) });
196 return Ok((payload, ctx));
197 }
198 LoopDecision::Continue => {
200 ctx.enriched_request.tool_choice = Some(ToolChoice::Auto);
201 append_output_items_to_input(&mut ctx.enriched_request.input, ¤t_output);
202 append_gateway_calls_to_new_input(&mut ctx, ¤t_output, ®istry);
203 append_tool_outputs(
204 &mut ctx,
205 gateway_results.into_iter().map(|result| result.input_item).collect(),
206 );
207 }
208 }
209 }
210
211 unreachable!("the final round returns Done, RequiresClientAction, or Incomplete");
212}
213
214async fn execute_and_emit_round_output_calls(
215 output_items: &[OutputItem],
216 registry: &ToolRegistry,
217 output_offset: usize,
218 deferred_events: Vec<EventFrame>,
219 ctx: &RequestContext,
220 stream: Option<(&mut GatewayStreamAccumulator, &mpsc::UnboundedSender<StreamEvent>)>,
221) -> ExecutorResult<Vec<GatewayCallResult>> {
222 match (deferred_events.is_empty(), stream) {
223 (true, stream) => execute_and_emit_output_calls(output_items, registry, output_offset, stream).await,
224 (false, Some((stream_accumulator, stream_sender))) => {
225 execute_and_emit_ordered_output_calls(
226 output_items,
227 registry,
228 output_offset,
229 deferred_events,
230 ctx,
231 stream_accumulator,
232 stream_sender,
233 )
234 .await
235 }
236 (false, None) => execute_and_emit_output_calls(output_items, registry, output_offset, None).await,
237 }
238}
239
240async fn execute_and_emit_ordered_output_calls(
241 output_items: &[OutputItem],
242 registry: &ToolRegistry,
243 output_offset: usize,
244 deferred_events: Vec<EventFrame>,
245 ctx: &RequestContext,
246 stream_accumulator: &mut GatewayStreamAccumulator,
247 stream_sender: &mpsc::UnboundedSender<StreamEvent>,
248) -> ExecutorResult<Vec<GatewayCallResult>> {
249 let mut events_by_output = Vec::with_capacity(output_items.len());
250 events_by_output.resize_with(output_items.len(), Vec::new);
251 let mut remaining_events = Vec::new();
252 for frame in deferred_events {
253 let Some(output_index) = frame
254 .wire
255 .output_index
256 .and_then(|index| usize::try_from(index).ok())
257 .filter(|index| *index < events_by_output.len())
258 else {
259 remaining_events.push(frame);
260 continue;
261 };
262 events_by_output[output_index].push(frame);
263 }
264
265 let mut event_plans = gateway_event_plans(output_items, registry, output_offset);
266 let first_gateway_index = output_items
267 .iter()
268 .position(|item| matches!(item, OutputItem::FunctionCall(call) if is_gateway_owned_call(call, registry)));
269 let first_gateway_run_end = first_gateway_index
270 .filter(|start| {
271 !output_items[..*start]
272 .iter()
273 .any(|item| matches!(item, OutputItem::FunctionCall(call) if is_client_custom_call(call, registry)))
274 })
275 .map_or(0, |start| {
276 output_items[start..]
277 .iter()
278 .take_while(
279 |item| matches!(item, OutputItem::FunctionCall(call) if is_gateway_owned_call(call, registry)),
280 )
281 .count()
282 .saturating_add(start)
283 });
284 let first_gateway_run_len = first_gateway_run_end.saturating_sub(first_gateway_index.unwrap_or(0));
285 emit_gateway_start_events(&event_plans[..first_gateway_run_len], stream_accumulator, stream_sender)?;
286
287 let gateway_results = execute_output_calls(output_items, registry).await?;
288 complete_gateway_event_plans(&mut event_plans, &gateway_results);
289 let mut gateway_index = 0;
290 for (index, item) in output_items.iter().enumerate() {
291 if matches!(item, OutputItem::FunctionCall(call) if is_gateway_owned_call(call, registry)) {
292 let plan = &event_plans[gateway_index..=gateway_index];
293 let result = &gateway_results[gateway_index..=gateway_index];
294 if index >= first_gateway_run_end {
295 emit_gateway_start_events(plan, stream_accumulator, stream_sender)?;
296 }
297 emit_gateway_completed_events(result, plan, stream_accumulator, stream_sender)?;
298 emit_deferred_stream_events(
299 std::mem::take(&mut events_by_output[index]),
300 ctx,
301 registry,
302 stream_accumulator,
303 stream_sender,
304 output_offset,
305 )?;
306 gateway_index += 1;
307 } else {
308 emit_deferred_stream_events(
309 std::mem::take(&mut events_by_output[index]),
310 ctx,
311 registry,
312 stream_accumulator,
313 stream_sender,
314 output_offset,
315 )?;
316 }
317 }
318 emit_deferred_stream_events(
319 remaining_events,
320 ctx,
321 registry,
322 stream_accumulator,
323 stream_sender,
324 output_offset,
325 )?;
326 Ok(gateway_results)
327}
328
329fn finalize_loop(
333 payload: &mut ResponsePayload,
334 combined_output: Vec<crate::types::io::OutputItem>,
335 combined_usage: Option<ResponseUsage>,
336 ctx: &RequestContext,
337) {
338 payload.output = combined_output;
339 payload.usage = combined_usage;
340 ctx.inject_ids(payload);
341}
342
343async fn run_blocking(
344 ctx: RequestContext,
345 exec_ctx: &ExecutionContext,
346 auth: Option<&str>,
347) -> ExecutorResult<ResponsePayload> {
348 let (payload, ctx) = run_until_gateway_tools_complete(ctx, exec_ctx, auth, false, None).await?;
349
350 let ch = exec_ctx.conv_handler.clone();
351 let rh = exec_ctx.resp_handler.clone();
352 persist_if_needed(payload.clone(), ctx, ch, rh).await?;
353
354 Ok(payload)
355}
356
357fn run_stream(ctx: RequestContext, exec_ctx: Arc<ExecutionContext>, auth: Option<String>) -> BoxStream {
358 Box::pin(stream! {
359 let (event_tx, mut event_rx) = mpsc::unbounded_channel();
360 let exec_ctx_for_run = Arc::clone(&exec_ctx);
361 let event_tx_for_run = event_tx.clone();
362 let stream_accumulator = GatewayStreamAccumulator::new();
363 let mut run_handle = AbortOnDrop::new(tokio::spawn(async move {
364 let mut stream_accumulator = stream_accumulator;
365 let result = run_until_gateway_tools_complete(
366 ctx,
367 exec_ctx_for_run.as_ref(),
368 auth.as_deref(),
369 true,
370 Some((&mut stream_accumulator, &event_tx_for_run)),
371 )
372 .await;
373 (result, stream_accumulator)
374 }));
375
376 let mut next_sequence_number = 0;
377 loop {
378 tokio::select! {
379 Some(event) = event_rx.recv() => {
380 yield consume_stream_event(event, &mut next_sequence_number);
381 }
382 result = &mut run_handle.handle => {
383 match result {
384 Err(e) => {
385 for chunk in panicked_stream_chunks(&e, &mut event_rx, &mut next_sequence_number) {
386 yield chunk;
387 }
388 }
389 Ok((Err(e), mut stream_accumulator)) => {
390 while let Ok(event) = event_rx.try_recv() {
391 yield consume_stream_event(event, &mut next_sequence_number);
392 }
393 yield stream_accumulator.executor_error_chunk(&e);
394 yield DONE_MARKER.to_string();
395 }
396 Ok((Ok((payload, ctx)), mut stream_accumulator)) => {
397 while let Ok(event) = event_rx.try_recv() {
398 yield consume_stream_event(event, &mut next_sequence_number);
399 }
400 let ch = exec_ctx.conv_handler.clone();
405 let rh = exec_ctx.resp_handler.clone();
406 let mut terminal_accumulator = stream_accumulator.clone();
407 let terminal_chunk = terminal_accumulator.terminal_response_chunk(&payload);
408 match persist_if_needed(payload, ctx, ch, rh).await {
409 Ok(()) => match terminal_chunk {
410 Ok(chunk) => yield chunk,
411 Err(e) => yield stream_accumulator.executor_error_chunk(&e),
412 },
413 Err(e) => yield stream_accumulator.executor_error_chunk(&e),
414 }
415 yield DONE_MARKER.to_string();
416 }
417 }
418 break;
419 }
420 }
421 }
422 })
423}
424
425fn consume_stream_event(event: StreamEvent, next_sequence_number: &mut u64) -> String {
426 *next_sequence_number = event.sequence_number.saturating_add(1);
427 event.content
428}
429
430fn stream_task_failure_chunk(error: &tokio::task::JoinError, sequence_number: u64) -> String {
431 error_sse_chunk(&format!("stream task failed: {error}"), sequence_number)
432}
433
434fn panicked_stream_chunks(
435 error: &tokio::task::JoinError,
436 event_rx: &mut mpsc::UnboundedReceiver<StreamEvent>,
437 next_sequence_number: &mut u64,
438) -> Vec<String> {
439 let mut chunks = Vec::new();
440 while let Ok(event) = event_rx.try_recv() {
441 chunks.push(consume_stream_event(event, next_sequence_number));
442 }
443 chunks.push(stream_task_failure_chunk(error, *next_sequence_number));
444 chunks.push(DONE_MARKER.to_owned());
445 chunks
446}
447
448pub async fn create_conversation(exec_ctx: &ExecutionContext) -> ExecutorResult<crate::ConversationData> {
457 exec_ctx.conv_handler.create().await
458}
459
460pub struct ExecuteRequest {
466 payload: RequestPayload,
467 exec_ctx: Arc<ExecutionContext>,
468 client_auth: Option<String>,
469}
470
471impl ExecuteRequest {
472 #[must_use]
473 pub fn new(payload: RequestPayload, exec_ctx: Arc<ExecutionContext>) -> Self {
474 Self {
475 payload,
476 exec_ctx,
477 client_auth: None,
478 }
479 }
480
481 #[must_use]
483 pub fn with_auth(mut self, token: Option<String>) -> Self {
484 self.client_auth = token;
485 self
486 }
487
488 pub async fn run(self) -> ExecutorResult<Either<ResponsePayload, BoxStream>> {
497 debug!(
498 model = %self.payload.model,
499 store = self.payload.store,
500 stream = self.payload.stream,
501 has_previous_response_id = self.payload.previous_response_id.is_some(),
502 has_conversation_id = self.payload.conversation_id.is_some(),
503 tools = self.payload.tools.as_ref().map_or(0, Vec::len),
504 "executor received responses request"
505 );
506 let ctx = rehydrate_conversation(self.payload, &self.exec_ctx).await?;
507 if ctx.original_request.stream {
508 Ok(Either::Right(run_stream(ctx, self.exec_ctx, self.client_auth)))
509 } else {
510 Ok(Either::Left(
511 run_blocking(ctx, &self.exec_ctx, self.client_auth.as_deref()).await?,
512 ))
513 }
514 }
515}
516
517pub async fn execute(
524 request: RequestPayload,
525 exec_ctx: Arc<ExecutionContext>,
526) -> ExecutorResult<Either<ResponsePayload, BoxStream>> {
527 ExecuteRequest::new(request, exec_ctx).run().await
528}
529
530#[cfg(test)]
531mod tests {
532 use super::*;
533
534 #[tokio::test]
535 async fn stream_task_panic_after_event_uses_next_sequence_number_for_error() {
536 let accumulator = GatewayStreamAccumulator::new();
537 let (event_tx, mut event_rx) = mpsc::unbounded_channel();
538 let task = tokio::spawn(async move {
539 let mut accumulator = accumulator;
540 let event = accumulator
541 .process_sse_line(r#"data: {"type":"response.created"}"#, 0)
542 .expect("event should be emitted");
543 event_tx
544 .send(StreamEvent {
545 content: "event".to_owned(),
546 sequence_number: event.sequence_number().expect("event should be numbered"),
547 })
548 .expect("test receiver should remain open");
549 panic!("test task panic");
550 });
551
552 let error = task.await.expect_err("task should panic");
553 let mut next_sequence_number = 0;
554 let chunks = panicked_stream_chunks(&error, &mut event_rx, &mut next_sequence_number);
555 let mut error_lines = chunks[1].lines();
556 assert_eq!(error_lines.next(), Some("event: error"));
557 let error_data = error_lines
558 .next()
559 .and_then(|line| line.strip_prefix("data: "))
560 .expect("SSE data");
561 assert!(error_lines.all(str::is_empty), "unexpected SSE frame content");
562 let error_event: serde_json::Value =
563 serde_json::from_str(error_data).expect("error chunk should be valid JSON");
564
565 assert_eq!(chunks[0], "event");
566 assert_eq!(error_event["type"], "error");
567 assert_eq!(error_event["sequence_number"], 1);
568 assert_eq!(chunks[2], DONE_MARKER);
569 }
570}