use std::sync::Arc;
use crate::PromptContribution;
use crate::PromptFingerprint;
use crate::llm::types::{LlmOutputPart, LlmResponse, LlmToolSpec, ProviderReasoningReplay};
use crate::sansio::{
ChatContextProjector, ContextProjector, ProtocolDriverHandle, TurnProtocol, UnitTurnProtocol,
};
use crate::session_model::{Part, PartKind, PruneState};
pub type TurnLimitFinalMessage =
Arc<dyn Fn(String, usize) -> crate::Message + Send + Sync + 'static>;
#[derive(Clone)]
pub struct TurnDriverConfig<M: TurnProtocol = UnitTurnProtocol> {
pub protocol: Arc<dyn ProtocolDriverHandle<M>>,
pub projector: Arc<dyn ContextProjector<M>>,
pub sync_execution_surface: bool,
pub turn_limit_final_message: TurnLimitFinalMessage,
}
impl<M: TurnProtocol> TurnDriverConfig<M> {
pub fn chat(
protocol: Arc<dyn ProtocolDriverHandle<M>>,
sync_execution_surface: bool,
turn_limit_final_message: TurnLimitFinalMessage,
) -> Self {
Self {
protocol,
projector: Arc::new(ChatContextProjector),
sync_execution_surface,
turn_limit_final_message,
}
}
}
#[derive(Clone)]
pub struct TurnDriverPreamble<M: TurnProtocol = UnitTurnProtocol> {
pub config: TurnDriverConfig<M>,
pub tool_specs: Arc<Vec<LlmToolSpec>>,
pub tool_names: Arc<Vec<String>>,
pub tool_names_fingerprint: PromptFingerprint,
pub omitted_tool_count: usize,
pub execution_prompt: Arc<str>,
pub prompt_contributions: Vec<PromptContribution>,
}
pub fn normalized_response_parts(llm_response: &LlmResponse) -> Vec<LlmOutputPart> {
if llm_response.parts.is_empty() && !llm_response.full_text.is_empty() {
vec![LlmOutputPart::Text {
text: llm_response.full_text.clone(),
response_meta: None,
}]
} else {
llm_response.parts.clone()
}
}
pub fn reasoning_part(
asst_id: &str,
index: usize,
text: String,
meta: Option<ProviderReasoningReplay>,
) -> Part {
Part {
id: format!("{asst_id}.p{index}"),
kind: PartKind::Reasoning,
content: text,
attachment: None,
tool_call_id: None,
tool_name: None,
tool_replay: None,
prune_state: PruneState::Intact,
reasoning_meta: meta,
response_meta: None,
}
}
pub fn append_assistant_text_part(out: &mut String, next: &str) {
if out.is_empty() {
out.push_str(next);
return;
}
let prev_trailing_newlines = out.chars().rev().take_while(|ch| *ch == '\n').count();
let next_leading_newlines = next.chars().take_while(|ch| *ch == '\n').count();
let total_boundary_newlines = prev_trailing_newlines + next_leading_newlines;
if total_boundary_newlines < 2 {
out.push_str(&"\n".repeat(2 - total_boundary_newlines));
}
out.push_str(next);
}