use serde::{Deserialize, Serialize};
use crate::engine::stable_id;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ThinkingStep {
pub id: String,
pub order: u32,
pub step: String,
pub detail: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub summary: String,
pub level: String,
pub source_event: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parent_id: Option<String>,
}
impl ThinkingStep {
#[must_use]
pub fn new(
order: u32,
step: impl Into<String>,
detail: impl Into<String>,
level: impl Into<String>,
source_event: impl Into<String>,
) -> Self {
let step = step.into();
let detail = detail.into();
let level = level.into();
let source_event = source_event.into();
let summary = naturalize_thinking_step(&step, &detail);
let seed = format!("{order}:{step}:{detail}:{level}:{source_event}");
Self {
id: stable_id("thinking_step", &seed),
order,
step,
detail,
summary,
level,
source_event,
parent_id: None,
}
}
#[must_use]
pub fn with_parent(mut self, parent_id: impl Into<String>) -> Self {
self.parent_id = Some(parent_id.into());
self
}
}
#[must_use]
pub fn thinking_language_label(code: &str) -> String {
let normalized = code.trim().to_ascii_lowercase();
let primary = normalized
.split(['-', '_'])
.next()
.unwrap_or(normalized.as_str());
match primary {
"en" => "English".to_owned(),
"ru" => "Russian".to_owned(),
"hi" => "Hindi".to_owned(),
"zh" => "Chinese".to_owned(),
"" | "unknown" => "an unrecognized language".to_owned(),
other => other.to_owned(),
}
}
#[must_use]
pub fn humanize_meta_identifier(value: &str) -> String {
let mut spaced = String::with_capacity(value.len());
let mut previous_lower = false;
for character in value.chars() {
if character.is_ascii_uppercase() && previous_lower {
spaced.push(' ');
}
if matches!(character, '_' | ':' | '.' | '-' | '/') {
spaced.push(' ');
} else {
spaced.push(character);
}
previous_lower = character.is_ascii_lowercase() || character.is_ascii_digit();
}
let collapsed = spaced.split_whitespace().collect::<Vec<_>>().join(" ");
collapsed.trim().to_ascii_lowercase()
}
fn indefinite_article(phrase: &str) -> &'static str {
match phrase.trim_start().chars().next() {
Some(first) if matches!(first.to_ascii_lowercase(), 'a' | 'e' | 'i' | 'o' | 'u') => "an",
_ => "a",
}
}
fn strip_agent_substep_prefix(step: &str) -> &str {
if let Some(rest) = step.strip_prefix("agent_") {
if let Some(index) = rest.find('_') {
if index > 0 && rest[..index].bytes().all(|b| b.is_ascii_digit()) {
return &rest[index + 1..];
}
}
}
step
}
fn truncate_thinking_detail(value: &str) -> String {
let trimmed = value.trim();
let limit = 120;
if trimmed.chars().count() <= limit {
return trimmed.to_owned();
}
let truncated: String = trimmed.chars().take(limit - 1).collect();
format!("{}…", truncated.trim_end())
}
#[must_use]
pub fn naturalize_thinking_step(step: &str, detail: &str) -> String {
let canonical = strip_agent_substep_prefix(step);
let trimmed = truncate_thinking_detail(detail);
let has_detail = !trimmed.is_empty();
match canonical {
"impulse" => {
if has_detail {
format!("Read the request: \"{trimmed}\".")
} else {
"Read the incoming request.".to_owned()
}
}
"detect_language" => {
format!(
"Detect the request language: {}.",
thinking_language_label(detail)
)
}
"resolve_response_language" => {
format!("Plan to answer in {}.", thinking_language_label(detail))
}
"formalize" => {
if has_detail {
let task = humanize_meta_identifier(&trimmed);
format!(
"Formalize the request as {} {task} task.",
indefinite_article(&task)
)
} else {
"Formalize the request into a symbolic tuple.".to_owned()
}
}
"formalize_resolved" => {
if has_detail {
format!(
"Resolve the request to {}.",
humanize_meta_identifier(&trimmed)
)
} else {
"Resolve the request to a concrete entity.".to_owned()
}
}
"clarify_formalization" => {
if has_detail {
format!("Ask for clarification between {trimmed}.")
} else {
"Ask for clarification because the request was ambiguous.".to_owned()
}
}
"dispatch_handler" => {
if has_detail {
format!(
"Route to the {} handler.",
humanize_meta_identifier(&trimmed)
)
} else {
"Route the request to a handler.".to_owned()
}
}
"route_attempt" => {
if has_detail {
format!("Try the {} approach.", humanize_meta_identifier(&trimmed))
} else {
"Try the next candidate approach.".to_owned()
}
}
"match_rule" => {
if has_detail {
format!("Match the {} rule.", humanize_meta_identifier(&trimmed))
} else {
"Match a known rule.".to_owned()
}
}
"compute" => {
if has_detail {
format!("Compute {trimmed}.")
} else {
"Compute the result.".to_owned()
}
}
"compute_engine" => {
if has_detail {
format!("Evaluate with the {}.", humanize_meta_identifier(&trimmed))
} else {
"Evaluate with the calculator.".to_owned()
}
}
"compute_expression" => format!("Reduce the expression {trimmed}."),
"compute_steps" => format!("Apply {trimmed} reduction step(s)."),
"lookup_fact" => {
if has_detail {
format!("Look up {}.", humanize_meta_identifier(&trimmed))
} else {
"Look up the relevant fact.".to_owned()
}
}
"invoke_tool" => {
if has_detail {
format!("Use the {} capability.", humanize_meta_identifier(&trimmed))
} else {
"Use an available capability.".to_owned()
}
}
"rule_verification" => {
if has_detail {
format!(
"Verify the result against the {} rule.",
humanize_meta_identifier(&trimmed)
)
} else {
"Verify the result against the rules.".to_owned()
}
}
"policy_refusal" => {
if has_detail {
format!(
"Decline the request under the {} policy.",
humanize_meta_identifier(&trimmed)
)
} else {
"Decline the request under the safety policy.".to_owned()
}
}
"rule_construction" => "Build a local behavior rule.".to_owned(),
"coreference_binding" => "Resolve what the follow-up refers to.".to_owned(),
"modifier_detection" => "Detect modifiers in the request.".to_owned(),
"program_plan" => {
if has_detail {
format!("Plan the program: {}.", humanize_meta_identifier(&trimmed))
} else {
"Plan the requested program.".to_owned()
}
}
"scan_memory" => {
if has_detail {
format!("Search memory for {trimmed}.")
} else {
"Search memory for relevant facts.".to_owned()
}
}
"user_context" => {
if has_detail {
format!("Apply available context: {trimmed}.")
} else {
"Apply the available context.".to_owned()
}
}
"deformalize" => {
if has_detail {
format!("Compose the answer: \"{trimmed}\".")
} else {
"Compose the answer in natural language.".to_owned()
}
}
"http_chat" => "Exchange a request with the configured endpoint.".to_owned(),
"agent_plan" => {
if has_detail {
format!("Add an agent task: {}.", humanize_meta_identifier(&trimmed))
} else {
"Extend the agent plan.".to_owned()
}
}
"memory" => "Update the local memory bundle.".to_owned(),
"extract_term" => "Extract the search term.".to_owned(),
"group_by_conversation" => "Group matching memories by conversation.".to_owned(),
"fallback" => "Fall back to the general unknown-request strategy.".to_owned(),
other => {
let readable = humanize_meta_identifier(other);
let label = if readable.is_empty() {
"step".to_owned()
} else {
readable
};
if has_detail {
format!("{label}: {trimmed}.")
} else {
format!("{label}.")
}
}
}
}