use pointlock_ir::{JsonSchemaDocument, RunLogPayload, RunPath, render_run_path};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use super::ProjectionVersion;
use crate::error::StoreError;
use crate::store::Store;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct HumanInboxEntry {
pub projection_version: ProjectionVersion,
pub run_id: String,
pub flow_id: String,
pub request_id: String,
pub purpose: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub mode: Option<String>,
pub prompt: String,
pub presents: Value,
#[serde(skip_serializing_if = "Option::is_none")]
pub decisions: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub output_schema: Option<JsonSchemaDocument>,
#[serde(skip_serializing_if = "Option::is_none")]
pub deadline_at_ms: Option<u64>,
pub requested_at_ms: u64,
pub requested_seq: u64,
pub run_path: String,
pub run_path_frames: RunPath,
}
pub fn run_inbox(store: &Store, run_id: &str) -> Result<Vec<HumanInboxEntry>, StoreError> {
let meta = store.run_meta(run_id)?;
let events = store.events(run_id)?;
let mut pending: Vec<HumanInboxEntry> = Vec::new();
for event in &events {
match &event.payload {
RunLogPayload::HumanRequested {
request_id,
purpose,
mode,
prompt,
presents,
decisions,
output_schema,
deadline_at_ms,
} => pending.push(HumanInboxEntry {
projection_version: ProjectionVersion,
run_id: run_id.to_owned(),
flow_id: meta.flow_id.to_string(),
request_id: request_id.clone(),
purpose: wire(purpose),
mode: mode.as_ref().map(wire),
prompt: prompt.clone(),
presents: presents.clone(),
decisions: decisions.clone(),
output_schema: output_schema.clone(),
deadline_at_ms: *deadline_at_ms,
requested_at_ms: event.at_ms,
requested_seq: event.seq,
run_path: render_run_path(&event.run_path),
run_path_frames: event.run_path.clone(),
}),
RunLogPayload::HumanResponded {
request_id,
purpose,
response,
..
} => {
let non_final = *purpose == pointlock_ir::HumanPurpose::Supervision
&& response.get("decision").and_then(Value::as_str) == Some("suspend");
if !non_final {
pending.retain(|entry| entry.request_id != *request_id);
}
}
RunLogPayload::StepExited { .. } => {
pending.retain(|entry| {
!crate::fold::exit_settles_pending(&event.run_path, &entry.run_path_frames)
});
}
_ => {}
}
}
Ok(pending)
}
pub fn human_inbox(store: &Store) -> Result<Vec<HumanInboxEntry>, StoreError> {
let mut entries = Vec::new();
for run in store.list_runs()? {
entries.extend(run_inbox(store, &run.run_id)?);
}
Ok(entries)
}
fn wire<T: Serialize>(value: &T) -> String {
serde_json::to_value(value)
.ok()
.and_then(|v| v.as_str().map(str::to_owned))
.unwrap_or_default()
}