tandem-server 0.6.9

HTTP server for Tandem engine APIs
use super::*;

pub(super) fn session_context_run_event_input(
    event: &EngineEvent,
) -> Option<ContextRunEventAppendInput> {
    match event.event_type.as_str() {
        "session.run.started" => Some(ContextRunEventAppendInput {
            event_type: "session_run_started".to_string(),
            status: ContextRunStatus::Running,
            step_id: Some("session-run".to_string()),
            payload: serde_json::json!({
                "sessionID": event.properties.get("sessionID").cloned().unwrap_or(Value::Null),
                "runID": event.properties.get("runID").cloned().unwrap_or(Value::Null),
                "agentID": event.properties.get("agentID").cloned().unwrap_or(Value::Null),
                "agentProfile": event.properties.get("agentProfile").cloned().unwrap_or(Value::Null),
                "tenantContext": event_tenant_context_value(event),
                "why_next_step": "session run in progress",
                "step_status": "in_progress",
            }),
        }),
        "message.part.updated" => {
            if is_running_tool_args_delta(&event.properties) {
                return None;
            }
            let part = event.properties.get("part")?;
            let part_type = part
                .get("type")
                .and_then(|value| value.as_str())
                .unwrap_or_default();
            if !matches!(
                part_type,
                "tool" | "tool-invocation" | "tool-result" | "tool_invocation" | "tool_result"
            ) {
                return None;
            }
            let tool_name = part
                .get("tool")
                .and_then(|value| value.as_str())
                .unwrap_or("tool");
            let tool_state = part
                .get("state")
                .and_then(|value| value.as_str())
                .unwrap_or("running");
            let why_next_step = match tool_state {
                "completed" => format!("tool `{tool_name}` completed"),
                "failed" => format!("tool `{tool_name}` failed"),
                _ => format!("tool `{tool_name}` running"),
            };
            Some(ContextRunEventAppendInput {
                event_type: "session_tool_updated".to_string(),
                status: ContextRunStatus::Running,
                step_id: Some("session-run".to_string()),
                payload: serde_json::json!({
                    "sessionID": event.properties.get("sessionID").cloned().unwrap_or(Value::Null),
                    "runID": event.properties.get("runID").cloned().unwrap_or(Value::Null),
                    "part": compact_context_tool_value(Some(part)),
                    "toolCallDelta": compact_context_tool_value(event.properties.get("toolCallDelta")),
                    "tenantContext": event_tenant_context_value(event),
                    "why_next_step": why_next_step,
                    "step_status": if tool_state == "completed" { "done" } else { "in_progress" },
                    "error": part.get("error").cloned().unwrap_or(Value::Null),
                }),
            })
        }
        "session.run.finished" => {
            let status = event
                .properties
                .get("status")
                .and_then(|value| value.as_str())
                .unwrap_or("completed");
            Some(ContextRunEventAppendInput {
                event_type: "session_run_finished".to_string(),
                status: session_run_status_to_context(status),
                step_id: Some("session-run".to_string()),
                payload: serde_json::json!({
                    "sessionID": event.properties.get("sessionID").cloned().unwrap_or(Value::Null),
                    "runID": event.properties.get("runID").cloned().unwrap_or(Value::Null),
                    "status": status,
                    "error": event.properties.get("error").cloned().unwrap_or(Value::Null),
                    "tenantContext": event_tenant_context_value(event),
                    "why_next_step": format!("session run finished with status `{status}`"),
                    "step_status": if matches!(status, "completed") { "done" } else if matches!(status, "cancelled") { "blocked" } else { "failed" },
                }),
            })
        }
        "tool.effect.recorded" => {
            let record = event.properties.get("record")?;
            let tool = record
                .get("tool")
                .and_then(|value| value.as_str())
                .unwrap_or("tool");
            let status = record
                .get("status")
                .and_then(|value| value.as_str())
                .unwrap_or("started");
            let phase = record
                .get("phase")
                .and_then(|value| value.as_str())
                .unwrap_or("invocation");
            let summary = match status {
                "succeeded" => format!("tool `{tool}` {phase} succeeded"),
                "failed" => format!("tool `{tool}` {phase} failed"),
                "blocked" => format!("tool `{tool}` {phase} blocked"),
                _ => format!("tool `{tool}` {phase} started"),
            };
            Some(ContextRunEventAppendInput {
                event_type: "tool_effect_recorded".to_string(),
                status: ContextRunStatus::Running,
                step_id: Some("session-run".to_string()),
                payload: serde_json::json!({
                    "sessionID": event.properties.get("sessionID").cloned().unwrap_or(Value::Null),
                    "messageID": event.properties.get("messageID").cloned().unwrap_or(Value::Null),
                    "tool": event.properties.get("tool").cloned().unwrap_or(Value::Null),
                    "record": record.clone(),
                    "tenantContext": event_tenant_context_value(event),
                    "why_next_step": summary,
                    "step_status": if matches!(status, "failed" | "blocked") {
                        "blocked"
                    } else {
                        "in_progress"
                    },
                }),
            })
        }
        "tool.routing.decision" => Some(ContextRunEventAppendInput {
            event_type: "tool_routing_decision".to_string(),
            status: ContextRunStatus::Running,
            step_id: Some("session-run".to_string()),
            payload: serde_json::json!({
                "sessionID": event.properties.get("sessionID").cloned().unwrap_or(Value::Null),
                "messageID": event.properties.get("messageID").cloned().unwrap_or(Value::Null),
                "iteration": event.properties.get("iteration").cloned().unwrap_or(Value::Null),
                "mode": event.properties.get("mode").cloned().unwrap_or(Value::Null),
                "intent": event.properties.get("intent").cloned().unwrap_or(Value::Null),
                "selectedToolCount": event.properties.get("selectedToolCount").cloned().unwrap_or(Value::Null),
                "totalAvailableTools": event.properties.get("totalAvailableTools").cloned().unwrap_or(Value::Null),
                "offeredTools": event.properties.get("offeredTools").cloned().unwrap_or(Value::Array(Vec::new())),
                "hiddenByScope": event.properties.get("hiddenByScope").cloned().unwrap_or(Value::Array(Vec::new())),
                "strictProjectionActive": event.properties.get("strictProjectionActive").cloned().unwrap_or(Value::Bool(false)),
                "scopeAllowlist": event.properties.get("scopeAllowlist").cloned().unwrap_or(Value::Array(Vec::new())),
                "tenantContext": event_tenant_context_value(event),
                "why_next_step": "tool routing manifest recorded",
                "step_status": "in_progress",
            }),
        }),
        "policy.decision.recorded" => {
            let record = event.properties.get("record")?;
            let decision = record
                .get("decision")
                .and_then(Value::as_str)
                .unwrap_or("unknown");
            let tool = record
                .get("tool")
                .and_then(Value::as_str)
                .unwrap_or("runtime");
            Some(ContextRunEventAppendInput {
                event_type: "policy_decision_recorded".to_string(),
                status: if decision == "allow" {
                    ContextRunStatus::Running
                } else {
                    ContextRunStatus::Blocked
                },
                step_id: Some("policy-decision".to_string()),
                payload: serde_json::json!({
                    "sessionID": event.properties.get("sessionID").cloned().unwrap_or(Value::Null),
                    "messageID": event.properties.get("messageID").cloned().unwrap_or(Value::Null),
                    "runID": event.properties.get("runID").cloned().unwrap_or(Value::Null),
                    "automationID": event.properties.get("automationID").cloned().unwrap_or(Value::Null),
                    "tool": event.properties.get("tool").cloned().unwrap_or(Value::Null),
                    "decisionID": event.properties.get("decisionID").cloned().unwrap_or(Value::Null),
                    "record": record.clone(),
                    "tenantContext": event_tenant_context_value(event),
                    "why_next_step": format!("policy decision `{decision}` recorded for `{tool}`"),
                    "step_status": if decision == "allow" {
                        "in_progress"
                    } else {
                        "blocked"
                    },
                }),
            })
        }
        "mutation.checkpoint.recorded" => {
            let record = event.properties.get("record")?;
            let tool = record
                .get("tool")
                .and_then(|value| value.as_str())
                .unwrap_or("tool");
            let outcome = record
                .get("outcome")
                .and_then(|value| value.as_str())
                .unwrap_or("succeeded");
            let changed_file_count = record
                .get("changed_file_count")
                .and_then(|value| value.as_u64())
                .unwrap_or(0);
            Some(ContextRunEventAppendInput {
                event_type: "mutation_checkpoint_recorded".to_string(),
                status: ContextRunStatus::Running,
                step_id: Some("session-run".to_string()),
                payload: serde_json::json!({
                    "sessionID": event.properties.get("sessionID").cloned().unwrap_or(Value::Null),
                    "messageID": event.properties.get("messageID").cloned().unwrap_or(Value::Null),
                    "tool": event.properties.get("tool").cloned().unwrap_or(Value::Null),
                    "record": record.clone(),
                    "tenantContext": event_tenant_context_value(event),
                    "why_next_step": format!(
                        "mutation checkpoint for `{tool}` recorded with outcome `{outcome}` and {changed_file_count} changed files"
                    ),
                    "step_status": if matches!(outcome, "failed" | "blocked") {
                        "blocked"
                    } else {
                        "in_progress"
                    },
                }),
            })
        }
        _ => None,
    }
}