1#![allow(clippy::print_stdout)]
2use serde::Serialize;
6use std::process::ExitCode;
7
8pub struct Output {
13 format: agent_first_data::OutputFormat,
14}
15
16#[allow(clippy::print_stdout)]
17impl Output {
18 pub fn new(format: agent_first_data::OutputFormat) -> Self {
19 Self { format }
20 }
21
22 fn format(&self, value: &serde_json::Value) -> String {
23 agent_first_data::cli_output(value, self.format)
24 }
25
26 pub fn ok<T: Serialize>(&self, result: T) -> ExitCode {
28 let result_value = serde_json::to_value(&result).unwrap_or_default();
29 let mut resp = agent_first_data::build_json_ok(result_value, None);
30 agent_first_data::internal_redact_secrets(&mut resp);
31 println!("{}", self.format(&resp));
32 ExitCode::SUCCESS
33 }
34
35 pub fn ok_trace<T: Serialize>(&self, result: T, trace: impl Serialize) -> ExitCode {
37 let result_value = serde_json::to_value(&result).unwrap_or_default();
38 let trace_value = serde_json::to_value(&trace).unwrap_or_default();
39 let mut resp = agent_first_data::build_json_ok(result_value, Some(trace_value));
40 agent_first_data::internal_redact_secrets(&mut resp);
41 println!("{}", self.format(&resp));
42 ExitCode::SUCCESS
43 }
44
45 pub fn error(&self, error_code: &str, message: &str) -> ExitCode {
47 self.error_hint(error_code, message, None)
48 }
49
50 pub fn error_hint(&self, error_code: &str, message: &str, hint: Option<&str>) -> ExitCode {
52 let mut fields = serde_json::Map::new();
53 fields.insert(
54 "error".into(),
55 serde_json::Value::String(message.to_string()),
56 );
57 if let Some(h) = hint {
58 fields.insert("hint".into(), serde_json::Value::String(h.to_string()));
59 }
60 let mut resp = agent_first_data::build_json(
61 error_code,
62 serde_json::Value::Object(fields),
63 Some(serde_json::json!({"duration_ms": 0})),
64 );
65 agent_first_data::internal_redact_secrets(&mut resp);
66 println!("{}", self.format(&resp));
67 ExitCode::FAILURE
68 }
69
70 pub fn error_from(&self, error_code: &str, err: &anyhow::Error) -> ExitCode {
72 self.error(error_code, &err.to_string())
73 }
74
75 pub fn error_hypha(&self, err: &crate::HyphaError) -> ExitCode {
77 self.error_hint(&err.code, &err.message, err.hint.as_deref())
78 }
79
80 pub fn progress(&self, step: u32, total: u32, message: &str, data: serde_json::Value) {
83 let mut fields = match data {
84 serde_json::Value::Object(map) => map,
85 _ => serde_json::Map::new(),
86 };
87 fields.insert("current".into(), step.into());
88 fields.insert("total".into(), total.into());
89 fields.insert("message".into(), message.into());
90 let mut resp =
91 agent_first_data::build_json("progress", serde_json::Value::Object(fields), None);
92 agent_first_data::internal_redact_secrets(&mut resp);
93 println!("{}", self.format(&resp));
94 }
95
96 pub fn download_progress(&self, downloaded_bytes: u64, total_bytes: Option<u64>) {
99 let mut resp = agent_first_data::build_json(
100 "download_progress",
101 serde_json::json!({
102 "downloaded_bytes": downloaded_bytes,
103 "total_bytes": total_bytes,
104 }),
105 None,
106 );
107 agent_first_data::internal_redact_secrets(&mut resp);
108 println!("{}", self.format(&resp));
109 }
110
111 pub fn warn(&self, code: &str, message: &str) {
113 let mut resp =
114 agent_first_data::build_json(code, serde_json::json!({"message": message}), None);
115 agent_first_data::internal_redact_secrets(&mut resp);
116 println!("{}", self.format(&resp));
117 }
118
119 pub fn startup(&self, args: serde_json::Value) {
121 let cfg = crate::config::HyphaConfig::load();
122 let config = serde_json::to_value(&cfg).unwrap_or_default();
123
124 let env = serde_json::json!({
125 "CMN_HOME": std::env::var("CMN_HOME").ok(),
126 "SYNAPSE_TOKEN_SECRET": std::env::var("SYNAPSE_TOKEN_SECRET").ok(),
127 });
128 let mut resp = agent_first_data::build_json(
129 "log",
130 serde_json::json!({
131 "event": "startup",
132 "hypha_version": env!("CARGO_PKG_VERSION"),
133 "config": config,
134 "args": args,
135 "env": env
136 }),
137 None,
138 );
139 agent_first_data::internal_redact_secrets(&mut resp);
140 println!("{}", self.format(&resp));
141 }
142}
143
144pub struct OutSink<'a>(pub &'a Output);
149
150impl crate::EventSink for OutSink<'_> {
151 fn emit(&self, event: crate::HyphaEvent) {
152 match event {
153 crate::HyphaEvent::Progress {
154 current,
155 total,
156 message,
157 } => {
158 self.0
159 .progress(current, total, &message, serde_json::Value::Null);
160 }
161 crate::HyphaEvent::DownloadProgress {
162 downloaded_bytes,
163 total_bytes,
164 } => {
165 self.0.download_progress(downloaded_bytes, total_bytes);
166 }
167 crate::HyphaEvent::Log { message } => {
168 self.0.warn("log", &message);
169 }
170 crate::HyphaEvent::Warn { message } => {
171 self.0.warn("warn", &message);
172 }
173 }
174 }
175}