Coverage Report

Created: 2026-07-25 01:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/runner/work/lyquor/lyquor/ldk/lyquid-flow/src/step.rs
Line
Count
Source
1
use std::collections::BTreeMap;
2
use std::fmt;
3
use std::sync::Arc;
4
5
use serde::{Deserialize, Serialize};
6
use serde_json::Value;
7
8
/// Stable identifier of a Step.
9
#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
10
pub struct StepId(pub String);
11
12
impl From<&str> for StepId {
13
0
    fn from(value: &str) -> Self {
14
0
        Self(value.to_owned())
15
0
    }
16
}
17
18
impl fmt::Display for StepId {
19
0
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
20
0
        formatter.write_str(&self.0)
21
0
    }
22
}
23
24
/// Typed key-value data shared by Steps.
25
// TODO: now state type is described as string. We may want to use developer-owned
26
//       generic Rust State and derive its model schema instead of introducing a
27
//       separate StateType system.
28
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
29
pub struct State {
30
    /// Field declarations sorted by name for deterministic rendering and serialization.
31
    pub schema: BTreeMap<String, StateFieldSpec>,
32
33
    /// Values in the same deterministic order, shared with Trace snapshots through `Arc`.
34
    pub values: Arc<BTreeMap<String, Value>>,
35
}
36
37
/// Type and semantic description of one State field.
38
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
39
pub struct StateFieldSpec {
40
    /// Type name used to validate replacement values.
41
    pub value_type: String,
42
43
    /// Meaning supplied to clients or model context.
44
    pub description: String,
45
}
46
47
/// Ordered history of completed Steps.
48
// TODO: Arc-backed entries still retain full State snapshots. Consider
49
//       storing committed StateUpdates as deltas to avoid Trace growing
50
//       with the full State size. And we may compact and prune Trace entries.
51
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
52
pub struct Trace(
53
    /// Entries in completion order.
54
    pub Vec<TraceEntry>,
55
);
56
57
/// One completed Step and its copy-on-write State snapshot.
58
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
59
pub struct TraceEntry {
60
    /// Step that produced this committed snapshot.
61
    pub step_id: StepId,
62
63
    /// State values after that Step completed.
64
    pub state: Arc<BTreeMap<String, Value>>,
65
}
66
67
/// Client-facing input contract for a Step.
68
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
69
pub struct InputSpec {
70
    /// Prompt shown to the client.
71
    pub prompt: String,
72
73
    /// JSON Schema used to validate the submitted value.
74
    pub schema: Value,
75
}
76
77
/// Runtime value accepted through an InputSpec.
78
pub type Input = Value;
79
80
/// Whole-field State replacements proposed by one Step.
81
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
82
pub struct StateUpdates(
83
    /// Replacement values indexed by existing State field name.
84
    pub BTreeMap<String, Value>,
85
);
86
87
/// The Step selected after the current Step finishes, or its final result.
88
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
89
#[serde(rename_all = "snake_case", tag = "kind", content = "value")]
90
pub enum Next {
91
    /// Continue execution at another registered Step.
92
    Step(StepId),
93
94
    /// Finish the run with this value.
95
    Done(Value),
96
}
97
98
/// Model prompt configuration, or a decision that skips the model.
99
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
100
#[serde(rename_all = "snake_case", tag = "kind")]
101
pub enum PromptSpec {
102
    /// Ask the developer's model integration to make the decision.
103
    Prompt {
104
        /// Step-specific prompt template.
105
        template: String,
106
107
        /// Number of recent Trace entries to include.
108
        trace_count: usize,
109
    },
110
111
    /// Make a deterministic decision without calling a model.
112
    Skip {
113
        /// Step or final result selected directly by this Step.
114
        next: Next,
115
116
        /// State replacements selected directly by this Step.
117
        updates: StateUpdates,
118
    },
119
}
120
121
/// One workflow graph vertex.
122
pub trait Step: Send + Sync {
123
    /// Describes the input required for this step, or returns `None` for no input.
124
    fn input(&self, state: &State, trace: &Trace) -> Option<InputSpec>;
125
126
    /// Explains this Step's purpose; `concise` requests a shorter form for history.
127
    fn description(&self, state: &State, concise: bool) -> String;
128
129
    /// Selects a model prompt or provides the decision directly.
130
    fn prompt(&self) -> PromptSpec;
131
132
    /// Adjusts the proposed next move and State updates before they are committed and may
133
    /// perform external actions if needed.
134
    fn post_prompt(&self, state: &State, trace: &Trace, next: &mut Next, updates: &mut StateUpdates);
135
}