use std::collections::HashSet;
use runtime::{
CancelFlag, GenerationConfig, RawDecodeResult, RuntimeError, TurnEvent, TurnSplitter,
};
use tokenizer::{ParsedToolCall, ReasoningEffort};
use super::plan::AppState;
use crate::cancel::Cancel;
pub(crate) enum GenError {
Runtime(RuntimeError),
Join(String),
}
pub(crate) enum Piece {
Text(String),
Reasoning(String),
Tool(ParsedToolCall),
}
pub(crate) struct Generated {
pub text: String,
pub reasoning: String,
pub calls: Vec<ParsedToolCall>,
pub decode: RawDecodeResult,
pub released_span_text: String,
}
pub(crate) struct RunControl {
pub cancel: Cancel,
pub permit: Option<crate::queue::GenerationPermit>,
}
pub(crate) fn cancelled_before_start() -> RawDecodeResult {
RawDecodeResult {
reused_prefix_tokens: 0,
session_slot_evicted: false,
prompt_tokens: 0,
new_tokens: 0,
prefill_seconds: 0.0,
decode_seconds: 0.0,
reason: runtime::StopReason::Cancelled,
kv_position: 0,
kv_backed_token_ids: Vec::new(),
peak_memory_pressure: runtime::MemoryPressure::Normal,
}
}
pub(crate) async fn run_full(
model: AppState,
prompt_ids: Vec<foundation::TokenId>,
config: GenerationConfig,
images: Option<crate::vision::RequestImages>,
tools: HashSet<String>,
effort: ReasoningEffort,
control: RunControl,
) -> Result<Generated, GenError> {
let RunControl { cancel, permit } = control;
let joined = crate::queue::run_blocking(permit, move || {
let mut text = String::new();
let mut reasoning = String::new();
let mut calls = Vec::new();
let flag = crate::cancel::as_cancel_flag(&cancel);
let result = stream_blocking(
&model,
&prompt_ids,
&config,
images.as_ref(),
&tools,
effort,
&flag,
&mut |piece| match piece {
Piece::Text(delta) => text.push_str(&delta),
Piece::Reasoning(delta) => reasoning.push_str(&delta),
Piece::Tool(call) => calls.push(call),
},
);
(result, text, reasoning, calls)
})
.await;
match joined {
Ok((Ok((decode, released_span_text)), text, reasoning, calls)) => Ok(Generated {
text,
reasoning,
calls,
decode,
released_span_text,
}),
Ok((Err(e), ..)) => Err(GenError::Runtime(e)),
Err(e) => Err(GenError::Join(e.to_string())),
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn stream_blocking(
model: &AppState,
prompt_ids: &[foundation::TokenId],
config: &GenerationConfig,
images: Option<&crate::vision::RequestImages>,
tools: &HashSet<String>,
effort: ReasoningEffort,
cancel: CancelFlag<'_>,
on_piece: &mut dyn FnMut(Piece),
) -> Result<(RawDecodeResult, String), RuntimeError> {
let mut next_id = 0usize;
let mut split = TurnSplitter::new(
model.tokenizer(),
tools,
effort,
move || {
next_id += 1;
format!("toolu_{}", next_id - 1)
},
prompt_ids,
);
let mut released_span_text = String::new();
let mut emit_piece = |event: TurnEvent| match event {
TurnEvent::Prefill { .. } => {}
TurnEvent::Content(delta) => on_piece(Piece::Text(delta)),
TurnEvent::ReleasedToolSpan(delta) => {
released_span_text.push_str(&delta);
on_piece(Piece::Text(delta));
}
TurnEvent::Reasoning(delta) => on_piece(Piece::Reasoning(delta)),
TurnEvent::ToolCall(call) => on_piece(Piece::Tool(call)),
};
let result = model.run_completion(prompt_ids, config, images, cancel, &mut |e| {
split.feed(e, &mut emit_piece);
});
if result.is_ok() {
let _ = split.finish(&mut emit_piece);
}
result.map(|decode| (decode, released_span_text))
}