Skip to main content

kindling_types/
observation.rs

1use serde::{Deserialize, Serialize};
2use serde_json::{Map, Value};
3
4#[cfg(feature = "ts-rs")]
5use ts_rs::TS;
6
7use crate::common::{Id, ScopeIds, Timestamp};
8
9/// Types of observations that can be captured.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
11#[cfg_attr(feature = "ts-rs", derive(TS), ts(export, export_to = "../bindings/"))]
12#[serde(rename_all = "snake_case")]
13pub enum ObservationKind {
14    ToolCall,
15    Command,
16    FileDiff,
17    Error,
18    Message,
19    NodeStart,
20    NodeEnd,
21    NodeOutput,
22    NodeError,
23}
24
25impl ObservationKind {
26    /// All variants in declaration order. Mirrors `OBSERVATION_KINDS` in TS.
27    pub const ALL: &'static [ObservationKind] = &[
28        ObservationKind::ToolCall,
29        ObservationKind::Command,
30        ObservationKind::FileDiff,
31        ObservationKind::Error,
32        ObservationKind::Message,
33        ObservationKind::NodeStart,
34        ObservationKind::NodeEnd,
35        ObservationKind::NodeOutput,
36        ObservationKind::NodeError,
37    ];
38}
39
40/// Atomic, immutable record of an event captured during development.
41///
42/// Mirrors `Observation` in `packages/kindling-core/src/types/observation.ts`.
43/// The `redacted` flag is the only mutable field (via explicit redaction APIs).
44#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
45#[cfg_attr(feature = "ts-rs", derive(TS), ts(export, export_to = "../bindings/"))]
46#[serde(rename_all = "camelCase")]
47pub struct Observation {
48    pub id: Id,
49    pub kind: ObservationKind,
50    pub content: String,
51    /// Source-specific metadata stored as a JSON object.
52    pub provenance: Map<String, Value>,
53    #[cfg_attr(feature = "ts-rs", ts(type = "number"))]
54    pub ts: Timestamp,
55    pub scope_ids: ScopeIds,
56    pub redacted: bool,
57}
58
59/// Input for creating a new observation. Optional fields are auto-generated.
60#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
61#[cfg_attr(feature = "ts-rs", derive(TS), ts(export, export_to = "../bindings/"))]
62#[serde(rename_all = "camelCase")]
63pub struct ObservationInput {
64    #[serde(default, skip_serializing_if = "Option::is_none")]
65    #[cfg_attr(feature = "ts-rs", ts(optional))]
66    pub id: Option<Id>,
67    pub kind: ObservationKind,
68    pub content: String,
69    #[serde(default, skip_serializing_if = "Option::is_none")]
70    #[cfg_attr(feature = "ts-rs", ts(optional))]
71    pub provenance: Option<Map<String, Value>>,
72    #[serde(default, skip_serializing_if = "Option::is_none")]
73    #[cfg_attr(feature = "ts-rs", ts(optional, type = "number"))]
74    pub ts: Option<Timestamp>,
75    pub scope_ids: ScopeIds,
76    #[serde(default, skip_serializing_if = "Option::is_none")]
77    #[cfg_attr(feature = "ts-rs", ts(optional))]
78    pub redacted: Option<bool>,
79}