Skip to main content

kindling_client/
body.rs

1//! Wire-facing request bodies for the v1 API.
2//!
3//! These mirror the camelCase shapes in `kindling-server`'s `dto.rs` exactly.
4//! Response bodies are the domain types from `kindling-types` (already
5//! camelCase) and are deserialized directly — no response DTOs needed.
6
7use kindling_types::{CapsuleType, Id, ObservationInput, PinTargetType, ScopeIds};
8use serde::Serialize;
9
10/// `POST /v1/capsules` body — open a capsule.
11#[derive(Debug, Clone, Serialize)]
12#[serde(rename_all = "camelCase")]
13pub(crate) struct OpenCapsuleBody {
14    pub kind: CapsuleType,
15    pub intent: String,
16    pub scope_ids: ScopeIds,
17    #[serde(skip_serializing_if = "Option::is_none")]
18    pub id: Option<Id>,
19}
20
21/// `PATCH /v1/capsules/:id/close` body — all fields optional.
22#[derive(Debug, Clone, Default, Serialize)]
23#[serde(rename_all = "camelCase")]
24pub struct CloseCapsuleBody {
25    /// Ask the daemon to generate a summary on close.
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub generate_summary: Option<bool>,
28    /// Provide summary content directly.
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub summary_content: Option<String>,
31    /// Confidence for a provided summary, in `[0.0, 1.0]`.
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub confidence: Option<f64>,
34}
35
36/// `POST /v1/observations` body — `ObservationInput` flattened plus the
37/// top-level routing/append options (`capsuleId`, `validate`).
38#[derive(Debug, Clone, Serialize)]
39#[serde(rename_all = "camelCase")]
40pub(crate) struct AppendObservationBody {
41    #[serde(flatten)]
42    pub input: ObservationInput,
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub capsule_id: Option<Id>,
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub validate: Option<bool>,
47}
48
49/// `POST /v1/pins` body — create a pin.
50#[derive(Debug, Clone, Serialize)]
51#[serde(rename_all = "camelCase")]
52pub struct CreatePinBody {
53    /// What kind of entity is being pinned.
54    pub target_type: PinTargetType,
55    /// The id of the observation or summary to pin.
56    pub target_id: Id,
57    /// Optional free-text note.
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub note: Option<String>,
60    /// Optional time-to-live in milliseconds; the pin expires after this.
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub ttl_ms: Option<i64>,
63    /// Optional scope override for the pin.
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub scope_ids: Option<ScopeIds>,
66}
67
68impl CreatePinBody {
69    /// Construct a minimal pin body for `target_type`/`target_id`.
70    pub fn new(target_type: PinTargetType, target_id: impl Into<Id>) -> Self {
71        Self {
72            target_type,
73            target_id: target_id.into(),
74            note: None,
75            ttl_ms: None,
76            scope_ids: None,
77        }
78    }
79}
80
81/// `POST /v1/context/session-start` body. Both fields optional on the wire;
82/// the client always sends `scopeIds` (a repo scope built from the project
83/// root) to reproduce the Node hook's `{ repoId: <project root> }` filter.
84#[derive(Debug, Clone, Serialize)]
85#[serde(rename_all = "camelCase")]
86pub(crate) struct SessionStartContextBody {
87    #[serde(skip_serializing_if = "Option::is_none")]
88    pub max_results: Option<u32>,
89    pub scope_ids: ScopeIds,
90}
91
92/// `POST /v1/context/pre-compact` body.
93#[derive(Debug, Clone, Serialize)]
94#[serde(rename_all = "camelCase")]
95pub(crate) struct PreCompactContextBody {
96    pub scope_ids: ScopeIds,
97}