Skip to main content

vasari_core/schema/
action.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use serde_json::json;
4
5use super::NodeId;
6use crate::hash::node_id;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct Action {
10    pub id: NodeId,
11    pub tool: String,
12    /// Hashed if the args contain sensitive values (e.g., credentials, PII).
13    pub args: serde_json::Value,
14    /// Human-readable summary of the result — EXCLUDED from content hash.
15    pub result_summary: String,
16    pub timestamp: DateTime<Utc>,
17    pub plan_ref: PlanRef,
18    pub parent_ids: Vec<NodeId>,
19    #[serde(default = "default_schema_version")]
20    pub schema_version: String,
21}
22
23fn default_schema_version() -> String {
24    "1".to_string()
25}
26
27/// Reference to a specific step within a Plan.
28/// Steps are sub-records of Plan nodes, not standalone nodes.
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct PlanRef {
31    pub plan_id: NodeId,
32    pub step_index: usize,
33}
34
35impl Action {
36    pub fn new(
37        tool: String,
38        args: serde_json::Value,
39        result_summary: String,
40        timestamp: DateTime<Utc>,
41        plan_ref: PlanRef,
42        parent_ids: Vec<NodeId>,
43    ) -> Self {
44        let id = NodeId(node_id(&Self::hash_input(
45            &tool,
46            &args,
47            &timestamp,
48            &plan_ref,
49            &parent_ids,
50        )));
51        Self {
52            id,
53            tool,
54            args,
55            result_summary,
56            timestamp,
57            plan_ref,
58            parent_ids,
59            schema_version: default_schema_version(),
60        }
61    }
62
63    /// result_summary is intentionally excluded — it's an annotation, not identity.
64    fn hash_input(
65        tool: &str,
66        args: &serde_json::Value,
67        timestamp: &DateTime<Utc>,
68        plan_ref: &PlanRef,
69        parent_ids: &[NodeId],
70    ) -> serde_json::Value {
71        json!({
72            "type": "action",
73            "tool": tool,
74            "args": args,
75            "timestamp": timestamp.to_rfc3339(),
76            "plan_ref": {
77                "plan_id": plan_ref.plan_id.as_str(),
78                "step_index": plan_ref.step_index,
79            },
80            "parent_ids": parent_ids.iter().map(|id| id.as_str()).collect::<Vec<_>>(),
81        })
82    }
83}