use serde_json::Value;
use crate::protocol::ChatMessage;
use crate::seed::{
ROLE_TOOL_RESULT_DETAIL_REQUEST, ROLE_TOOL_RESULT_FIRST_REFERENCE,
ROLE_TOOL_RESULT_LINE_REQUEST, ROLE_TOOL_RESULT_SECOND_REFERENCE, ROLE_TOOL_RESULT_URL_REQUEST,
};
struct NormalizedResult {
payload: String,
error: Option<String>,
format: &'static str,
}
pub(super) fn render(label: &str, raw: &str, prompt: &str) -> String {
let result = normalize(raw);
let language = response_language(prompt);
if let Some(error) = result.error {
return fill("tool_result_failed", language, label, "", "", &error);
}
if result.payload.trim().is_empty() {
let intent = if is_listing(label) {
"tool_result_empty_list"
} else if is_search(label) {
"tool_result_empty_search"
} else {
"tool_result_empty_generic"
};
return fill(intent, language, label, "", "", "");
}
fill(
"tool_result_completed",
language,
label,
result.format,
&result.payload,
"",
)
}
pub(super) fn latest_turn_answer(
messages: &[ChatMessage],
tool_names: &[&str],
prompt: &str,
) -> Option<String> {
let start = messages
.iter()
.rposition(|message| message.role.eq_ignore_ascii_case("user"))?;
let (index, result) = messages
.iter()
.enumerate()
.skip(start + 1)
.rev()
.find(|(_, message)| message.role.eq_ignore_ascii_case("tool"))?;
if is_write_run_recipe(messages, tool_names) {
return None;
}
let label = result_label(messages, index);
Some(render(&label, &result.content.plain_text(), prompt))
}
pub(super) fn has_latest_turn_result(messages: &[ChatMessage]) -> bool {
let Some(start) = messages
.iter()
.rposition(|message| message.role.eq_ignore_ascii_case("user"))
else {
return false;
};
messages
.iter()
.skip(start + 1)
.any(|message| message.role.eq_ignore_ascii_case("tool"))
}
fn is_write_run_recipe(messages: &[ChatMessage], tool_names: &[&str]) -> bool {
let is_write = |name: &str| {
let lower = name.to_ascii_lowercase();
lower.contains("write") || lower.contains("create_file")
};
let is_run = |name: &str| {
let lower = name.to_ascii_lowercase();
lower.contains("run")
|| lower.contains("bash")
|| lower.contains("command")
|| lower.contains("exec")
|| lower.contains("shell")
};
tool_names.iter().copied().any(is_write)
&& tool_names.iter().copied().any(is_run)
&& messages
.iter()
.flat_map(|message| &message.tool_calls)
.any(|call| is_write(&call.function.name))
}
pub(super) fn follow_up_answer(messages: &[ChatMessage], prompt: &str) -> Option<String> {
let normalized_prompt = crate::engine::normalize_prompt(prompt);
let lexicon = crate::seed::lexicon();
let wants_url = lexicon.mentions_role(ROLE_TOOL_RESULT_URL_REQUEST, &normalized_prompt);
let wants_line = lexicon.mentions_role(ROLE_TOOL_RESULT_LINE_REQUEST, &normalized_prompt);
let wants_detail = lexicon.mentions_role(ROLE_TOOL_RESULT_DETAIL_REQUEST, &normalized_prompt);
if !wants_url && !wants_line && !wants_detail {
return None;
}
let latest_user = messages
.iter()
.rposition(|message| message.role.eq_ignore_ascii_case("user"))?;
let result = messages[..latest_user]
.iter()
.rev()
.find(|message| message.role.eq_ignore_ascii_case("tool"))?;
let result = normalize(&result.content.plain_text());
if wants_url {
let urls = extract_urls(&result.payload);
return urls
.get(requested_index(lexicon, &normalized_prompt))
.cloned();
}
if wants_line {
return result
.payload
.lines()
.nth(requested_index(lexicon, &normalized_prompt))
.map(str::to_owned);
}
Some(result.payload)
}
fn requested_index(lexicon: &crate::seed::Lexicon, prompt: &str) -> usize {
if lexicon.mentions_role(ROLE_TOOL_RESULT_SECOND_REFERENCE, prompt) {
return 1;
}
if lexicon.mentions_role(ROLE_TOOL_RESULT_FIRST_REFERENCE, prompt) {
return 0;
}
prompt
.split(|character: char| !character.is_ascii_digit())
.find_map(|digits| digits.parse::<usize>().ok())
.unwrap_or(1)
.saturating_sub(1)
}
fn normalize(raw: &str) -> NormalizedResult {
let trimmed = raw.trim();
let Ok(value) = serde_json::from_str::<Value>(trimmed) else {
return from_payload(&strip_transport_envelope(trimmed), None);
};
let Some(object) = value.as_object() else {
return from_payload(&pretty_json(&value), None);
};
let nonzero_exit = ["exit_code", "exitCode"]
.iter()
.filter_map(|key| object.get(*key))
.any(nonzero_status);
let failed_http = object
.get("status_code")
.and_then(Value::as_u64)
.is_some_and(|status| status >= 400);
let failed_status = object.get("status").is_some_and(failed_status);
let explicitly_unsuccessful = object.get("success").and_then(Value::as_bool) == Some(false);
let explicit_error = ["error", "stderr", "failure"]
.iter()
.filter_map(|key| object.get(*key))
.find_map(nonempty_text);
if nonzero_exit
|| failed_http
|| failed_status
|| explicitly_unsuccessful
|| explicit_error.is_some()
{
let error = explicit_error
.or_else(|| object.get("output").and_then(nonempty_text))
.or_else(|| {
["exit_code", "exitCode", "status_code", "status"]
.iter()
.find_map(|key| object.get(*key).map(|value| format!("{key}={value}")))
})
.unwrap_or_default();
return from_payload("", Some(error));
}
if let Some(payload) = ["output", "stdout", "content", "result"]
.iter()
.find_map(|key| object.get(*key))
{
let payload = payload
.as_str()
.map_or_else(|| pretty_json(payload), strip_transport_envelope);
return from_payload(&payload, None);
}
from_payload(&pretty_json(&value), None)
}
fn from_payload(payload: &str, error: Option<String>) -> NormalizedResult {
let trimmed = payload.trim().to_owned();
if let Ok(json) = serde_json::from_str::<Value>(&trimmed) {
return NormalizedResult {
payload: pretty_json(&json),
error,
format: "json",
};
}
let format = if trimmed.starts_with("#!/bin/bash") || trimmed.starts_with("#!/usr/bin/env bash")
{
"bash"
} else if trimmed.starts_with("#!/usr/bin/env python") {
"python"
} else {
"text"
};
NormalizedResult {
payload: trimmed,
error,
format,
}
}
fn strip_transport_envelope(text: &str) -> String {
let inner = text
.split_once("<untrusted_context>")
.and_then(|(_, rest)| rest.split_once("</untrusted_context>"))
.map_or(text, |(inside, _)| inside);
inner
.lines()
.filter_map(|line| {
let trimmed = line.trim();
if trimmed.starts_with("Process Group PGID:") {
None
} else if let Some(output) = trimmed.strip_prefix("Output:") {
let output = output.trim();
(!matches!(output, "(empty)" | "(no output)")).then_some(output)
} else {
Some(line)
}
})
.collect::<Vec<_>>()
.join("\n")
.trim()
.to_owned()
}
fn nonzero_status(value: &Value) -> bool {
value.as_i64().is_some_and(|status| status != 0)
|| value.as_str().is_some_and(|status| {
status.parse::<i64>().map_or_else(
|_| !matches!(status, "ok" | "success" | "completed"),
|status| status != 0,
)
})
}
fn failed_status(value: &Value) -> bool {
let numeric = value.as_i64().or_else(|| value.as_str()?.parse().ok());
numeric.map_or_else(
|| {
value
.as_str()
.is_some_and(|status| !matches!(status, "ok" | "success" | "completed" | "passed"))
},
|status| status < 0 || (0 < status && status < 100) || status >= 400,
)
}
fn nonempty_text(value: &Value) -> Option<String> {
match value {
Value::Null => None,
Value::String(text) => (!text.trim().is_empty()).then(|| text.trim().to_owned()),
Value::Object(object) => object
.get("message")
.and_then(nonempty_text)
.or_else(|| serde_json::to_string(value).ok()),
other => serde_json::to_string(other).ok(),
}
}
fn pretty_json(value: &Value) -> String {
serde_json::to_string_pretty(value).unwrap_or_else(|_| value.to_string())
}
fn result_tool(messages: &[ChatMessage], index: usize) -> Option<&str> {
let message = &messages[index];
message.name.as_deref().or_else(|| {
let id = message.tool_call_id.as_deref()?;
messages[..index]
.iter()
.flat_map(|prior| &prior.tool_calls)
.find(|call| call.id == id)
.map(|call| call.function.name.as_str())
})
}
fn result_label(messages: &[ChatMessage], index: usize) -> String {
let message = &messages[index];
let call = message.tool_call_id.as_deref().and_then(|id| {
messages[..index]
.iter()
.flat_map(|prior| &prior.tool_calls)
.find(|call| call.id == id)
});
if let Some(command) = call
.and_then(|call| serde_json::from_str::<Value>(&call.function.arguments).ok())
.as_ref()
.and_then(|arguments| arguments.get("command").or_else(|| arguments.get("cmd")))
.and_then(Value::as_str)
{
return command.to_owned();
}
result_tool(messages, index).unwrap_or("tool").to_owned()
}
fn extract_urls(text: &str) -> Vec<String> {
let mut urls = Vec::new();
let mut rest = text;
while let Some(start) = [rest.find("https://"), rest.find("http://")]
.into_iter()
.flatten()
.min()
{
let candidate = &rest[start..];
let end = candidate
.find(|character: char| character.is_whitespace() || "\"'<>)]}".contains(character))
.unwrap_or(candidate.len());
urls.push(candidate[..end].to_owned());
rest = &candidate[end..];
}
urls
}
fn is_listing(label: &str) -> bool {
let lower = label.to_ascii_lowercase();
lower.split_whitespace().next() == Some("ls")
|| lower.contains("list")
|| lower.contains("glob")
}
fn is_search(label: &str) -> bool {
let lower = label.to_ascii_lowercase();
["grep", "find", "search"]
.iter()
.any(|kind| lower.contains(kind))
}
fn response_language(prompt: &str) -> &'static str {
match crate::language::detect(prompt).slug() {
"ru" => "ru",
"hi" => "hi",
"zh" => "zh",
_ => "en",
}
}
fn fill(
intent: &str,
language: &str,
tool: &str,
format: &str,
payload: &str,
error: &str,
) -> String {
const TOOL_PLACEHOLDER: &str = "{tool}";
const FORMAT_PLACEHOLDER: &str = "{format}";
const PAYLOAD_PLACEHOLDER: &str = "{payload}";
const ERROR_PLACEHOLDER: &str = "{error}";
crate::seed::response_for(intent, language)
.or_else(|| crate::seed::response_for(intent, "en"))
.unwrap_or_default()
.replace(TOOL_PLACEHOLDER, tool)
.replace(FORMAT_PLACEHOLDER, format)
.replace(PAYLOAD_PLACEHOLDER, payload)
.replace(ERROR_PLACEHOLDER, error)
}