use lifeloop::router::{
BuiltinAdapterRegistry, CapabilityRequest, LifeloopReceiptEmitter, ReceiptContext, negotiate,
route,
};
use lifeloop::{CallbackRequest, CallbackResponse, LifecycleReceipt};
use serde::Deserialize;
use super::{CliError, parse_stdin_json, print_json};
#[derive(Deserialize)]
struct EmitInput {
request: CallbackRequest,
response: CallbackResponse,
context: WireReceiptContext,
}
#[derive(Deserialize)]
struct WireReceiptContext {
client_id: String,
receipt_id: String,
at_epoch_s: u64,
#[serde(default)]
parent_receipt_id: Option<String>,
#[serde(default)]
harness_session_id: Option<String>,
#[serde(default)]
harness_run_id: Option<String>,
#[serde(default)]
harness_task_id: Option<String>,
}
impl From<WireReceiptContext> for ReceiptContext {
fn from(w: WireReceiptContext) -> Self {
Self {
client_id: w.client_id,
receipt_id: w.receipt_id,
parent_receipt_id: w.parent_receipt_id,
at_epoch_s: w.at_epoch_s,
harness_session_id: w.harness_session_id,
harness_run_id: w.harness_run_id,
harness_task_id: w.harness_task_id,
}
}
}
pub fn run<I: Iterator<Item = String>>(mut args: I) -> Result<(), CliError> {
let action = args
.next()
.ok_or_else(|| CliError::Usage("receipt requires a subcommand: emit | show".to_string()))?;
if args.next().is_some() {
return Err(CliError::Usage(format!(
"receipt {action}: unexpected extra argument"
)));
}
match action.as_str() {
"emit" => run_emit(),
"show" => run_show(),
other => Err(CliError::Usage(format!(
"receipt: unknown subcommand `{other}` (expected: emit | show)"
))),
}
}
fn run_emit() -> Result<(), CliError> {
let input: EmitInput = parse_stdin_json("receipt emit envelope")?;
input
.request
.validate()
.map_err(|e| CliError::Validation(format!("CallbackRequest failed validation: {e}")))?;
input
.response
.validate()
.map_err(|e| CliError::Validation(format!("CallbackResponse failed validation: {e}")))?;
let registry = BuiltinAdapterRegistry;
let plan = route(&input.request, ®istry)
.map_err(|e| CliError::Validation(format!("router rejected request: {e}")))?;
let cap_request = CapabilityRequest::new();
let negotiated = negotiate(&plan, &cap_request, &[]);
let ctx: ReceiptContext = input.context.into();
let emitter = LifeloopReceiptEmitter::in_memory();
let receipt = emitter
.synthesize_and_emit(&negotiated, &input.response, &ctx)
.map_err(|e| CliError::Validation(format!("receipt emission failed: {e}")))?;
print_json(&receipt)
}
fn run_show() -> Result<(), CliError> {
let receipt: LifecycleReceipt = parse_stdin_json("LifecycleReceipt")?;
receipt
.validate()
.map_err(|e| CliError::Validation(format!("receipt failed validation: {e}")))?;
print_json(&receipt)
}