use anyhow::{Context, Result};
use super::chat_impl::preprocess_for_render;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct ThinkingVars {
pub preserve_thinking: Option<bool>,
pub thinking: Option<bool>,
}
#[derive(Debug, Clone, Copy, Default)]
pub(crate) struct RenderFlags<'a> {
pub enable_thinking: bool,
pub disable_tool_steering: bool,
pub reasoning_effort: Option<&'a str>,
pub preserve_thinking: Option<bool>,
pub thinking: Option<bool>,
pub allow_continue_final: bool,
}
pub(crate) fn render_chat(
env: &minijinja::Environment<'static>,
messages: &[serde_json::Value],
tools: Option<&[serde_json::Value]>,
flags: RenderFlags<'_>,
) -> Result<String> {
let tmpl = env
.get_template("chat")
.context("Failed to get compiled template")?;
let (messages_for_render, enable_thinking) =
preprocess_for_render(messages, flags.enable_thinking);
let messages_val = minijinja::Value::from_serialize(&messages_for_render);
let tools_val = tools.map(minijinja::Value::from_serialize);
let continue_final = flags.allow_continue_final
&& messages
.last()
.and_then(|m| m.get("role"))
.and_then(|r| r.as_str())
== Some("assistant");
let reasoning_effort: minijinja::Value = if let Some(effort) = flags.reasoning_effort {
effort.into()
} else if enable_thinking {
"medium".into()
} else {
"none".into()
};
let preserve_thinking = flags
.preserve_thinking
.map(minijinja::Value::from)
.unwrap_or(minijinja::Value::UNDEFINED);
let ctx = minijinja::context! {
messages => messages_val,
tools => tools_val.unwrap_or(minijinja::Value::UNDEFINED),
add_generation_prompt => !continue_final,
enable_thinking => enable_thinking,
reasoning_effort => reasoning_effort,
preserve_thinking => preserve_thinking,
thinking => flags
.thinking
.map(minijinja::Value::from)
.unwrap_or(minijinja::Value::UNDEFINED),
disable_tool_steering => flags.disable_tool_steering,
add_vision_id => false,
};
let mut rendered = tmpl.render(ctx).map_err(|e| {
tracing::error!("Jinja template error: {e:#}");
anyhow::anyhow!("Failed to render Jinja chat template: {e}")
})?;
if continue_final {
let trimmed = rendered.trim_end();
let stripped = trimmed.strip_suffix("<|im_end|>").unwrap_or(trimmed);
rendered = stripped.to_string();
tracing::info!("continue_final_message: stripped trailing EOT for prefill A/B");
}
Ok(rendered)
}