use std::time::Duration;
use lifeloop::router::{
BuiltinAdapterRegistry, CapabilityRequest, LifeloopReceiptEmitter, ReceiptContext,
SubprocessCallbackInvoker, SubprocessInvokerConfig, negotiate, route,
};
use lifeloop::{CallbackResponse, DispatchEnvelope};
use super::{CliError, parse_stdin_json, print_json};
pub fn run<I: Iterator<Item = String>>(mut args: I) -> Result<(), CliError> {
let action = args
.next()
.ok_or_else(|| CliError::Usage("event requires a subcommand: invoke".to_string()))?;
match action.as_str() {
"invoke" => run_invoke(args),
other => Err(CliError::Usage(format!(
"event: unknown subcommand `{other}` (expected: invoke)"
))),
}
}
fn run_invoke<I: Iterator<Item = String>>(mut args: I) -> Result<(), CliError> {
let mut client_cmd: Option<String> = None;
let mut client_args: Vec<String> = Vec::new();
let mut timeout_ms: u64 = 5_000;
let mut client_id: String = "lifeloop-cli".to_string();
let mut receipt_id: String = "rcpt-cli".to_string();
let mut at_epoch_s: u64 = 0;
let mut in_process = false;
while let Some(arg) = args.next() {
match arg.as_str() {
"--client-cmd" => {
client_cmd = Some(require_value(&arg, args.next())?);
}
"--client-arg" => {
client_args.push(require_value(&arg, args.next())?);
}
"--timeout-ms" => {
let v = require_value(&arg, args.next())?;
timeout_ms = v.parse::<u64>().map_err(|e| {
CliError::Usage(format!("--timeout-ms must be a non-negative integer: {e}"))
})?;
}
"--client-id" => {
client_id = require_value(&arg, args.next())?;
}
"--receipt-id" => {
receipt_id = require_value(&arg, args.next())?;
}
"--at-epoch-s" => {
let v = require_value(&arg, args.next())?;
at_epoch_s = v.parse::<u64>().map_err(|e| {
CliError::Usage(format!("--at-epoch-s must be a non-negative integer: {e}"))
})?;
}
"--in-process" => {
in_process = true;
}
other => {
return Err(CliError::Usage(format!(
"event invoke: unknown flag `{other}`"
)));
}
}
}
if !in_process && client_cmd.is_none() {
return Err(CliError::Usage(
"event invoke: --client-cmd <path> is required (or pass --in-process)".into(),
));
}
let envelope: DispatchEnvelope = parse_stdin_json("DispatchEnvelope")?;
envelope
.validate()
.map_err(|e| CliError::Validation(format!("DispatchEnvelope failed validation: {e}")))?;
let request = &envelope.request;
let payloads = envelope.payloads.as_slice();
let registry = BuiltinAdapterRegistry;
let plan = route(request, ®istry)
.map_err(|e| CliError::Validation(format!("router rejected request: {e}")))?;
let cap_request = CapabilityRequest::new();
let negotiated = negotiate(&plan, &cap_request, payloads);
if negotiated.blocks_dispatch() {
let response = CallbackResponse::ok(lifeloop::ReceiptStatus::Failed);
let ctx = ReceiptContext {
client_id: client_id.clone(),
receipt_id: receipt_id.clone(),
parent_receipt_id: None,
at_epoch_s,
harness_session_id: envelope.request.harness_session_id.clone(),
harness_run_id: envelope.request.harness_run_id.clone(),
harness_task_id: envelope.request.harness_task_id.clone(),
};
let emitter = LifeloopReceiptEmitter::in_memory();
let receipt = emitter
.synthesize_and_emit(&negotiated, &response, &ctx)
.map_err(|e| CliError::Validation(format!("receipt emission failed: {e}")))?;
return print_json(&receipt);
}
let response = if in_process {
let resp = CallbackResponse::ok(lifeloop::ReceiptStatus::Delivered);
resp.validate().map_err(|e| {
CliError::Validation(format!("in-process response failed validation: {e}"))
})?;
resp
} else {
let cmd = client_cmd.expect("checked above");
let mut config = SubprocessInvokerConfig::new(cmd, Duration::from_millis(timeout_ms));
for ca in client_args {
config = config.arg(ca);
}
let invoker = SubprocessCallbackInvoker::new(config);
use lifeloop::router::CallbackInvoker;
invoker
.invoke(&plan, payloads)
.map_err(|e| CliError::Validation(format!("subprocess callback failed: {e}")))?
};
let ctx = ReceiptContext {
client_id,
receipt_id,
parent_receipt_id: None,
at_epoch_s,
harness_session_id: envelope.request.harness_session_id.clone(),
harness_run_id: envelope.request.harness_run_id.clone(),
harness_task_id: envelope.request.harness_task_id.clone(),
};
let emitter = LifeloopReceiptEmitter::in_memory();
let receipt = emitter
.synthesize_and_emit(&negotiated, &response, &ctx)
.map_err(|e| CliError::Validation(format!("receipt emission failed: {e}")))?;
print_json(&receipt)
}
fn require_value(flag: &str, value: Option<String>) -> Result<String, CliError> {
value.ok_or_else(|| CliError::Usage(format!("flag `{flag}` requires a value")))
}