#![allow(clippy::print_stdout)]
use serde::Serialize;
use std::process::ExitCode;
pub struct Output {
format: agent_first_data::OutputFormat,
}
#[allow(clippy::print_stdout)]
impl Output {
pub fn new(format: agent_first_data::OutputFormat) -> Self {
Self { format }
}
fn format(&self, value: &serde_json::Value) -> String {
agent_first_data::cli_output(value, self.format)
}
pub fn ok<T: Serialize>(&self, result: T) -> ExitCode {
let result_value = serde_json::to_value(&result).unwrap_or_default();
let mut resp = agent_first_data::build_json_ok(result_value, None);
agent_first_data::internal_redact_secrets(&mut resp);
println!("{}", self.format(&resp));
ExitCode::SUCCESS
}
pub fn ok_trace<T: Serialize>(&self, result: T, trace: impl Serialize) -> ExitCode {
let result_value = serde_json::to_value(&result).unwrap_or_default();
let trace_value = serde_json::to_value(&trace).unwrap_or_default();
let mut resp = agent_first_data::build_json_ok(result_value, Some(trace_value));
agent_first_data::internal_redact_secrets(&mut resp);
println!("{}", self.format(&resp));
ExitCode::SUCCESS
}
pub fn error(&self, error_code: &str, message: &str) -> ExitCode {
self.error_hint(error_code, message, None)
}
pub fn error_hint(&self, error_code: &str, message: &str, hint: Option<&str>) -> ExitCode {
let mut fields = serde_json::Map::new();
fields.insert(
"error".into(),
serde_json::Value::String(message.to_string()),
);
if let Some(h) = hint {
fields.insert("hint".into(), serde_json::Value::String(h.to_string()));
}
let mut resp = agent_first_data::build_json(
error_code,
serde_json::Value::Object(fields),
Some(serde_json::json!({"duration_ms": 0})),
);
agent_first_data::internal_redact_secrets(&mut resp);
println!("{}", self.format(&resp));
ExitCode::FAILURE
}
pub fn error_from(&self, error_code: &str, err: &anyhow::Error) -> ExitCode {
self.error(error_code, &err.to_string())
}
pub fn error_hypha(&self, err: &crate::HyphaError) -> ExitCode {
self.error_hint(&err.code, &err.message, err.hint.as_deref())
}
pub fn progress(&self, step: u32, total: u32, message: &str, data: serde_json::Value) {
let mut fields = match data {
serde_json::Value::Object(map) => map,
_ => serde_json::Map::new(),
};
fields.insert("current".into(), step.into());
fields.insert("total".into(), total.into());
fields.insert("message".into(), message.into());
let mut resp =
agent_first_data::build_json("progress", serde_json::Value::Object(fields), None);
agent_first_data::internal_redact_secrets(&mut resp);
println!("{}", self.format(&resp));
}
pub fn download_progress(&self, downloaded_bytes: u64, total_bytes: Option<u64>) {
let mut resp = agent_first_data::build_json(
"download_progress",
serde_json::json!({
"downloaded_bytes": downloaded_bytes,
"total_bytes": total_bytes,
}),
None,
);
agent_first_data::internal_redact_secrets(&mut resp);
println!("{}", self.format(&resp));
}
pub fn warn(&self, code: &str, message: &str) {
let mut resp =
agent_first_data::build_json(code, serde_json::json!({"message": message}), None);
agent_first_data::internal_redact_secrets(&mut resp);
println!("{}", self.format(&resp));
}
pub fn startup(&self, args: serde_json::Value) {
let cfg = crate::config::HyphaConfig::load();
let config = serde_json::to_value(&cfg).unwrap_or_default();
let env = serde_json::json!({
"CMN_HOME": std::env::var("CMN_HOME").ok(),
"SYNAPSE_TOKEN_SECRET": std::env::var("SYNAPSE_TOKEN_SECRET").ok(),
});
let mut resp = agent_first_data::build_json(
"log",
serde_json::json!({
"event": "startup",
"hypha_version": env!("CARGO_PKG_VERSION"),
"config": config,
"args": args,
"env": env
}),
None,
);
agent_first_data::internal_redact_secrets(&mut resp);
println!("{}", self.format(&resp));
}
}
pub struct OutSink<'a>(pub &'a Output);
impl crate::EventSink for OutSink<'_> {
fn emit(&self, event: crate::HyphaEvent) {
match event {
crate::HyphaEvent::Progress {
current,
total,
message,
} => {
self.0
.progress(current, total, &message, serde_json::Value::Null);
}
crate::HyphaEvent::DownloadProgress {
downloaded_bytes,
total_bytes,
} => {
self.0.download_progress(downloaded_bytes, total_bytes);
}
crate::HyphaEvent::Log { message } => {
self.0.warn("log", &message);
}
crate::HyphaEvent::Warn { message } => {
self.0.warn("warn", &message);
}
}
}
}