Skip to main content

agent_first_psql/
emit.rs

1use crate::output_fmt;
2use crate::types::Output;
3use agent_first_data::{OutputFormat, OutputTo};
4use serde_json::Value;
5use std::sync::OnceLock;
6
7static OUTPUT_TO: OnceLock<OutputTo> = OnceLock::new();
8
9/// Record the process-wide event destination the resolved invocation chose.
10///
11/// The destination is part of each combination's output contract, not a
12/// separate scan of argv: a finite query emits at most one terminal event and
13/// splits by kind, while `--mode pipe` and `--stream-rows` are ordered event
14/// streams whose contract has no `split` to select. Anything emitted before an
15/// invocation resolves — a rejected argv, a readonly capability refusal — falls
16/// back to the split default, which puts errors on stderr.
17pub fn set_output_to(value: OutputTo) {
18    let _ = OUTPUT_TO.set(value);
19}
20
21pub fn output_to() -> OutputTo {
22    OUTPUT_TO.get().copied().unwrap_or(OutputTo::Split)
23}
24
25pub fn emit_cli_error(
26    msg: &str,
27    hint: Option<&str>,
28    format: OutputFormat,
29) -> Result<(), agent_first_data::CliEmitterError> {
30    emit_coded_error(
31        crate::protocol::error_code::INVALID_REQUEST,
32        msg,
33        hint,
34        format,
35    )
36}
37
38/// Emit one error event under a caller-chosen classification.
39///
40/// Structural rejections carry the parser's own `cli_*` code, so an agent
41/// branches on `error.code` for those exactly as it does for domain failures.
42pub fn emit_coded_error(
43    code: &str,
44    msg: &str,
45    hint: Option<&str>,
46    format: OutputFormat,
47) -> Result<(), agent_first_data::CliEmitterError> {
48    let mut emitter =
49        agent_first_data::CliEmitter::from_output_to(output_to(), format).with_strict_protocol();
50    let event = agent_first_data::json_error(code, msg)
51        .hint_if_some(hint)
52        .build()
53        .map_err(agent_first_data::CliEmitterError::Build)?;
54    emitter.emit(event)
55}
56
57pub fn emit_event(
58    event: agent_first_data::Event,
59    format: OutputFormat,
60) -> Result<(), agent_first_data::CliEmitterError> {
61    let mut emitter =
62        agent_first_data::CliEmitter::from_output_to(output_to(), format).with_strict_protocol();
63    emitter.emit(event)
64}
65
66pub fn emit_value(
67    value: Value,
68    format: OutputFormat,
69) -> Result<(), agent_first_data::CliEmitterError> {
70    let mut emitter =
71        agent_first_data::CliEmitter::from_output_to(output_to(), format).with_strict_protocol();
72    emitter.emit_validated_value(value)
73}
74
75pub fn emit_output(
76    out: &Output,
77    format: OutputFormat,
78) -> Result<(), agent_first_data::CliEmitterError> {
79    output_fmt::emit_process_output(out, format, output_to())
80}
81
82// AFDATA injects the raw outcomes this writes (`--docs`, plain help), so it owns
83// the routing and the rule that a closed reader is success rather than failure.
84pub fn write_result_text(text: &str) -> std::io::Result<()> {
85    agent_first_data::write_raw(text, output_to())
86}