use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::assertion::AssertionIR;
use crate::expr::{Expr, ExprMap};
use crate::flow::FlowRef;
use crate::handler::{HandlerBinding, RetryPolicy};
use crate::primitives::{
ActionName, FeatureId, Hash, JsonSchemaDocument, OnTimeout, Protection, StepId, literal_marker,
};
use crate::vocab::{
ActChannel, CanonicalVerb, EffectClassAction, ExecutionMode, HumanMode, ObservationWhich,
};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct StepBase {
pub step_id: StepId,
pub effect_hash: Hash,
pub judge_hash: Hash,
#[serde(skip_serializing_if = "Option::is_none")]
#[schemars(length(min = 1))]
pub preflight: Option<Vec<AssertionIR>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub retry: Option<RetryPolicy>,
#[serde(skip_serializing_if = "Option::is_none")]
#[schemars(range(min = 1))]
pub timeout_ms: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
#[schemars(length(min = 1))]
pub handlers: Option<Vec<HandlerBinding>>,
pub checkpoint: bool,
}
literal_marker! {
ActionKind => "action"
}
literal_marker! {
AssertKind => "assert"
}
literal_marker! {
CallKind => "call"
}
literal_marker! {
HumanKind => "human"
}
literal_marker! {
IfKind => "if"
}
literal_marker! {
ForeachKind => "foreach"
}
literal_marker! {
LetKind => "let"
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(untagged)]
pub enum StepIR {
Action(ActionStepIR),
Assert(AssertStepIR),
Call(CallStepIR),
Human(HumanStepIR),
If(IfStepIR),
Foreach(ForeachStepIR),
Let(LetStepIR),
}
impl StepIR {
pub fn step_id(&self) -> &StepId {
&self.base().step_id
}
pub fn base(&self) -> &StepBase {
match self {
StepIR::Action(s) => &s.base,
StepIR::Assert(s) => &s.base,
StepIR::Call(s) => &s.base,
StepIR::Human(s) => &s.base,
StepIR::If(s) => &s.base,
StepIR::Foreach(s) => &s.base,
StepIR::Let(s) => &s.base,
}
}
pub fn kind(&self) -> &'static str {
match self {
StepIR::Action(_) => "action",
StepIR::Assert(_) => "assert",
StepIR::Call(_) => "call",
StepIR::Human(_) => "human",
StepIR::If(_) => "if",
StepIR::Foreach(_) => "foreach",
StepIR::Let(_) => "let",
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
#[schemars(deny_unknown_fields)]
pub struct ActionStepIR {
pub kind: ActionKind,
#[serde(flatten)]
pub base: StepBase,
#[serde(skip_serializing_if = "Option::is_none")]
pub verb: Option<CanonicalVerb>,
pub effect: EffectClassAction,
pub idempotent: bool,
pub binding: ActionBinding,
pub assertions: Vec<AssertionIR>,
#[serde(skip_serializing_if = "Option::is_none")]
pub outputs: Option<ExprMap>,
#[serde(skip_serializing_if = "Option::is_none")]
pub output_schema: Option<JsonSchemaDocument>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ActionBinding {
#[schemars(length(min = 1))]
pub attempts: Vec<BoundAttempt>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct BoundAttempt {
pub channel: ActChannel,
pub action_name: ActionName,
pub args: ExprMap,
#[serde(skip_serializing_if = "Option::is_none")]
pub requires_feature: Option<FeatureId>,
#[schemars(length(min = 1))]
pub accept_execution_modes: std::collections::BTreeSet<ExecutionMode>,
pub protection: Protection,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
#[schemars(deny_unknown_fields)]
pub struct AssertStepIR {
pub kind: AssertKind,
#[serde(flatten)]
pub base: StepBase,
pub observe: ObservationSource,
#[schemars(length(min = 1))]
pub assertions: Vec<AssertionIR>,
}
literal_marker! {
FreshMarker => "fresh"
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(untagged)]
pub enum ObservationSource {
Fresh(FreshMarker),
FromStep(ObservationFromStep),
}
impl ObservationSource {
pub fn fresh() -> Self {
ObservationSource::Fresh(FreshMarker::Value)
}
pub fn from_step(from_step: StepId, which: ObservationWhich) -> Self {
ObservationSource::FromStep(ObservationFromStep { from_step, which })
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[schemars(inline)]
pub struct ObservationFromStep {
pub from_step: StepId,
pub which: ObservationWhich,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
#[schemars(deny_unknown_fields)]
pub struct CallStepIR {
pub kind: CallKind,
#[serde(flatten)]
pub base: StepBase,
pub flow_ref: FlowRef,
pub inputs: ExprMap,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
#[schemars(
deny_unknown_fields,
extend(
"if" = { "properties": { "mode": { "const": "provideInput" } }, "required": ["mode"] },
"then" = { "required": ["outputSchema"] }
)
)]
pub struct HumanStepIR {
pub kind: HumanKind,
#[serde(flatten)]
pub base: StepBase,
pub mode: HumanMode,
#[schemars(length(min = 1, max = 16384))]
pub prompt: String,
pub presents: Vec<Expr>,
#[serde(skip_serializing_if = "Option::is_none")]
#[schemars(length(min = 1), inner(length(min = 1, max = 256)))]
pub decisions: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub output_schema: Option<JsonSchemaDocument>,
#[schemars(range(min = 1))]
pub timeout_ms: u64,
pub on_timeout: OnTimeout,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
#[schemars(deny_unknown_fields)]
pub struct IfStepIR {
pub kind: IfKind,
#[serde(flatten)]
pub base: StepBase,
pub cond: Expr,
#[schemars(length(min = 1))]
pub then: Vec<StepIR>,
#[serde(skip_serializing_if = "Option::is_none")]
#[schemars(length(min = 1))]
pub r#else: Option<Vec<StepIR>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
#[schemars(deny_unknown_fields)]
pub struct ForeachStepIR {
pub kind: ForeachKind,
#[serde(flatten)]
pub base: StepBase,
pub items: Expr,
pub r#as: crate::primitives::Identifier,
#[schemars(length(min = 1))]
pub body: Vec<StepIR>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
#[schemars(deny_unknown_fields)]
pub struct LetStepIR {
pub kind: LetKind,
#[serde(flatten)]
pub base: StepBase,
#[schemars(schema_with = "let_bindings_schema")]
pub bindings: ExprMap,
}
fn let_bindings_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
schemars::json_schema!({
"allOf": [generator.subschema_for::<ExprMap>()],
"type": "object",
"minProperties": 1
})
}