//! Deterministic, effect-free rendering for Kennedy sessions.
#![forbid(unsafe_code)]
use std::collections::BTreeMap;
use std::time::Duration;
use anyhow::Context as _;
use chrono::{DateTime, Datelike, Timelike, Utc};
use serde_json::Value;
/// One deterministic rendering request.
#[derive(Clone, Debug)]
pub enum RenderRequest<'a> {
LoadNodes {
changed_box_ids: &'a [String],
projected_boxes: &'a [(String, String)],
},
ProviderFooter {
result: &'a str,
footer: &'a str,
},
SlowTool {
text: &'a str,
elapsed: Duration,
},
WebSearch {
answer: &'a str,
sources: &'a [(String, String)],
},
WebFetch {
url: &'a str,
title: Option<&'a str>,
content_type: &'a str,
truncated: bool,
content: &'a str,
},
MediaAnnotation {
object_id: &'a str,
file_name: &'a str,
content_type: &'a str,
model: &'a str,
complete: bool,
incomplete_reason: Option<&'a str>,
text: &'a str,
},
AudioTranscription {
object_id: &'a str,
file_name: &'a str,
content_type: &'a str,
model: &'a str,
text: &'a str,
},
DocumentExtraction {
object_id: &'a str,
file_name: &'a str,
format: &'a str,
characters: usize,
truncated: bool,
text: &'a str,
},
UserFileMetadata {
ordinal: usize,
object_id: &'a str,
file_name: &'a str,
media_type: &'a str,
size_bytes: u64,
},
StagedTelegramMedia {
pending_id: &'a str,
kind: &'a str,
file_name: &'a str,
media_type: &'a str,
size_bytes: u64,
message_id: i64,
reused: bool,
},
CostSummary {
label: &'a str,
estimated_cost_usd_nanos: u64,
unpriced_calls: u64,
},
RuntimeDescription {
model: &'a str,
reasoning_effort: &'a str,
current_time: DateTime<Utc>,
},
FreeTimeOpening {
free_time: &'a Value,
},
WakeupOpening {
marker: DateTime<Utc>,
},
ControllerMessage {
mode: &'a str,
free_time: &'a Value,
},
CallKtoolDescription,
}
/// Renders a request without reading state or performing an effect.
pub fn render(request: RenderRequest<'_>) -> anyhow::Result<String> {
match request {
RenderRequest::LoadNodes {
changed_box_ids,
projected_boxes,
} => render_load_nodes(changed_box_ids, projected_boxes),
RenderRequest::ProviderFooter { result, footer } => {
if result.is_empty() {
Ok(footer.into())
} else {
Ok(format!("{result}\n\n{footer}"))
}
}
RenderRequest::SlowTool { text, elapsed } => {
let mut text = text.to_owned();
if elapsed > Duration::from_secs(3) {
if !text.is_empty() && !text.ends_with('\n') {
text.push('\n');
}
text.push_str(&format!("[tool duration: {:.3}s]", elapsed.as_secs_f64()));
}
Ok(text)
}
RenderRequest::WebSearch { answer, sources } => {
let mut text = answer.to_owned();
if !sources.is_empty() {
text.push_str("\n\nSources:");
for (title, url) in sources {
let title = if title.trim().is_empty() { url } else { title };
text.push_str("\n- ");
text.push_str(title);
if title != url {
text.push_str(": ");
text.push_str(url);
}
}
}
Ok(text)
}
RenderRequest::WebFetch {
url,
title,
content_type,
truncated,
content,
} => {
let mut text = format!("Source URL: {url}");
if let Some(title) = title.filter(|title| !title.trim().is_empty()) {
text.push_str("\nTitle: ");
text.push_str(title);
}
text.push_str("\nContent type: ");
text.push_str(content_type);
if truncated {
text.push_str("\nThe returned page text was truncated.");
}
text.push_str("\n\n");
text.push_str(content);
Ok(text)
}
RenderRequest::MediaAnnotation {
object_id,
file_name,
content_type,
model,
complete,
incomplete_reason,
text,
} => {
anyhow::ensure!(
!text.trim().is_empty(),
"media annotation response has no text"
);
let status = if complete { "complete" } else { "incomplete" };
let mut rendered = format!(
"Annotation for {object_id}\nFile: {file_name}\nContent type: {content_type}\nModel: {model}\nStatus: {status}"
);
if let Some(reason) = incomplete_reason.filter(|reason| !reason.trim().is_empty()) {
rendered.push_str("\nIncomplete reason: ");
rendered.push_str(reason);
}
rendered.push_str("\n\n");
rendered.push_str(text);
Ok(rendered)
}
RenderRequest::AudioTranscription {
object_id,
file_name,
content_type,
model,
text,
} => {
anyhow::ensure!(
!text.trim().is_empty(),
"audio transcription response has no text"
);
Ok(format!(
"Transcription for {object_id}\nFile: {file_name}\nContent type: {content_type}\nModel: {model}\nStatus: complete\n\n{text}"
))
}
RenderRequest::DocumentExtraction {
object_id,
file_name,
format,
characters,
truncated,
text,
} => Ok(format!(
"Extracted text for {object_id}\nFile: {file_name}\nFormat: {format}\nCharacters: {characters}\nTruncated: {truncated}\n\n{text}"
)),
RenderRequest::UserFileMetadata {
ordinal,
object_id,
file_name,
media_type,
size_bytes,
} => Ok(format!(
"User-provided file {ordinal}\nObject reference: {object_id}\nOriginal filename: {file_name}\nExtension: {}\nMIME type: {}\nSize: {size_bytes} bytes",
file_name_extension(file_name),
normalize_media_type(media_type),
)),
RenderRequest::StagedTelegramMedia {
pending_id,
kind,
file_name,
media_type,
size_bytes,
message_id,
reused,
} => Ok(format!(
"{} Telegram group media\nMessage ID: {message_id}\nObject: {pending_id}\nKind: {kind}\nOriginal filename: {file_name}\nExtension: {}\nMIME type: {}\nSize: {size_bytes} bytes\n\nUse Object {pending_id} with AnnotateMedia, GenerateImage (for images), TranscribeAudio, or ExtractDocumentText as appropriate.",
if reused {
"Reused already-staged"
} else {
"Staged"
},
file_name_extension(file_name),
normalize_media_type(media_type),
)),
RenderRequest::CostSummary {
label,
estimated_cost_usd_nanos,
unpriced_calls,
} => Ok(cost_summary(
label,
estimated_cost_usd_nanos,
unpriced_calls,
)),
RenderRequest::RuntimeDescription {
model,
reasoning_effort,
current_time,
} => Ok(format!(
"You are currently running on {model} with {reasoning_effort} thinking mode. The current date and time is {}.",
human_utc_datetime(current_time)
)),
RenderRequest::FreeTimeOpening { free_time } => {
let custom = free_time
.get("customPrompt")
.and_then(Value::as_str)
.unwrap_or_default();
if custom.trim().is_empty() {
Ok("Begin this self-time session.".into())
} else {
Ok(format!(
"Begin this self-time session.\n\nRequested focus:\n{custom}"
))
}
}
RenderRequest::WakeupOpening { marker } => Ok(format!(
"The time is {} UTC on {}. Determine whether you have any messages you would like to send the user",
marker.format("%H:%M"),
marker.format("%Y-%m-%d"),
)),
RenderRequest::ControllerMessage { mode, free_time } => {
render_controller_message(mode, free_time)
}
RenderRequest::CallKtoolDescription => Ok(
"Call one Kennedy Ktool. The provider function remains registered even if its explaining system-prompt box is dehydrated. Kennedy may display an object with an optional recipient-visible filename using {\"name\":\"EmitObject\",\"arguments\":{\"objectId\":\"AAECAwQF\",\"fileName\":\"report.pdf\"}}. She may make an out-of-band cold delivery to an authorized user's private Telegram chat, optionally with Kweb object attachments and per-attachment delivery filenames, from any session with {\"name\":\"SendTelegramDM\",\"arguments\":{\"user\":{\"telegramUserId\":42},\"message\":\"Exact message text.\",\"attachments\":[\"pending:1\",{\"objectId\":\"AAECAwQF\",\"fileName\":\"report.pdf\"}]}}. Kennedy may likewise send text and attachments to any known Telegram group, addressed by its canonical Kweb root, with {\"name\":\"SendTelegramGroupMessage\",\"arguments\":{\"group\":{\"rootNodeId\":\"AAAAAAAE\"},\"message\":\"Exact message text.\",\"attachments\":[\"pending:1\"]}}.".into(),
),
}
}
fn render_load_nodes(
changed_box_ids: &[String],
projected_boxes: &[(String, String)],
) -> anyhow::Result<String> {
if changed_box_ids.is_empty() {
return Ok("LoadNodes completed. The shared Kweb boxes were already current.".into());
}
let rendered = projected_boxes.iter().cloned().collect::<BTreeMap<_, _>>();
changed_box_ids
.iter()
.map(|box_id| {
rendered
.get(box_id)
.cloned()
.with_context(|| format!("updated Kweb box {box_id} is absent from the projection"))
})
.collect::<anyhow::Result<Vec<_>>>()
.map(|boxes| boxes.join("\n\n"))
}
fn normalize_media_type(value: &str) -> String {
value
.split(';')
.next()
.unwrap_or(value)
.trim()
.to_ascii_lowercase()
}
fn file_name_extension(file_name: &str) -> String {
file_name
.rsplit_once('.')
.and_then(|(stem, extension)| {
(!stem.is_empty() && !extension.is_empty()).then_some(extension)
})
.map(|extension| format!(".{extension}"))
.unwrap_or_else(|| "(none)".into())
}
fn cost_summary(label: &str, estimated_cost_usd_nanos: u64, unpriced_calls: u64) -> String {
let rounded_milli_pennies = estimated_cost_usd_nanos.saturating_add(5_000) / 10_000;
let pennies = format!(
"{}.{:03}",
rounded_milli_pennies / 1_000,
rounded_milli_pennies % 1_000
);
if unpriced_calls == 0 {
format!("Estimated {label}: {pennies} pennies at standard API rates.")
} else {
format!(
"Estimated {label}: {pennies} pennies at standard API rates; {unpriced_calls} provider {} could not be priced.",
if unpriced_calls == 1 { "call" } else { "calls" }
)
}
}
fn human_utc_datetime(value: DateTime<Utc>) -> String {
let day = value.day();
let suffix = match day % 100 {
11..=13 => "th",
_ => match day % 10 {
1 => "st",
2 => "nd",
3 => "rd",
_ => "th",
},
};
let hour = match value.hour() % 12 {
0 => 12,
hour => hour,
};
let period = if value.hour() < 12 { "am" } else { "pm" };
format!(
"{} {day}{suffix}, {}, {hour}:{:02}{period} UTC",
value.format("%B"),
value.year(),
value.minute()
)
}
fn deadline(value: &Value) -> Option<DateTime<Utc>> {
value
.get("deadlineAt")
.and_then(Value::as_str)
.and_then(|value| DateTime::parse_from_rfc3339(value).ok())
.map(|value| value.with_timezone(&Utc))
}
fn free_time_schedule(value: &Value) -> String {
deadline(value)
.map(|deadline| {
format!(
"The self-time deadline is {}.",
human_utc_datetime(deadline)
)
})
.unwrap_or_else(|| "The self-time deadline was not supplied.".into())
}
fn render_controller_message(mode: &str, free_time: &Value) -> anyhow::Result<String> {
match mode {
"conversation" => {
Ok("Continue the turn. Use tools if needed, then answer the user.".into())
}
"free-time" => Ok(format!(
"Continue self time. {}",
free_time_schedule(free_time)
)),
"wakeup" => Ok(
"Continue this autonomous wakeup session. Sending no message is a valid outcome; call EndSession when you have finished.".into(),
),
"ingress" => Ok(
"You are in a solo history-ingress session; there is no user to receive a conversational response. If you have completed all useful memory work, call EndSession now through the native call_ktool function with no arguments. A normal response does not end this session. If work remains, continue it with tools, then call EndSession when finished.".into(),
),
_ => anyhow::bail!("unknown controller mode {mode}"),
}
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::TimeZone as _;
use serde_json::json;
#[test]
fn web_search_and_footer_are_exact() {
let sources = vec![
("Title".into(), "https://one.example".into()),
(" ".into(), "https://two.example".into()),
];
assert_eq!(
render(RenderRequest::WebSearch {
answer: "Answer",
sources: &sources,
})
.unwrap(),
"Answer\n\nSources:\n- Title: https://one.example\n- https://two.example"
);
assert_eq!(
render(RenderRequest::ProviderFooter {
result: "done",
footer: "status",
})
.unwrap(),
"done\n\nstatus"
);
}
#[test]
fn runtime_and_controller_text_are_exact() {
let current_time = Utc.with_ymd_and_hms(2026, 8, 3, 13, 7, 0).unwrap();
assert_eq!(
render(RenderRequest::RuntimeDescription {
model: "model",
reasoning_effort: "high",
current_time,
})
.unwrap(),
"You are currently running on model with high thinking mode. The current date and time is August 3rd, 2026, 1:07pm UTC."
);
assert_eq!(
render(RenderRequest::ControllerMessage {
mode: "conversation",
free_time: &json!(null),
})
.unwrap(),
"Continue the turn. Use tools if needed, then answer the user."
);
}
#[test]
fn slow_duration_and_empty_annotation_match_boundaries() {
assert_eq!(
render(RenderRequest::SlowTool {
text: "ok",
elapsed: Duration::from_secs(3),
})
.unwrap(),
"ok"
);
assert_eq!(
render(RenderRequest::SlowTool {
text: "ok",
elapsed: Duration::from_millis(3001),
})
.unwrap(),
"ok\n[tool duration: 3.001s]"
);
assert!(
render(RenderRequest::MediaAnnotation {
object_id: "pending:1",
file_name: "image.png",
content_type: "image/png",
model: "model",
complete: true,
incomplete_reason: None,
text: " ",
})
.is_err()
);
}
}