Skip to main content

switchyard_libsy/core/
state.rs

1// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Built-in state shared by processors and classifiers across a session.
5
6use std::collections::BTreeMap;
7
8use serde::{Deserialize, Serialize};
9
10use crate::algorithms::util::tool_signals::ToolSignals;
11
12/// A value in a session's [`State`].
13#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
14#[serde(rename_all = "snake_case")]
15pub enum StateValue {
16    /// Text value.
17    String(String),
18    /// Nonnegative counter.
19    Count(u32),
20    /// Flag value.
21    Bool(bool),
22}
23
24/// Routing facts accumulated across one session's algorithm runs.
25#[derive(Debug, Clone, Default, Serialize, Deserialize)]
26pub struct State {
27    /// Tool-result signals for the current request, set by the tool-signal
28    /// processor. `None` until it runs or when the request has no tool activity,
29    /// so routers must treat absence as "no signal yet".
30    #[serde(skip)]
31    pub tool_signals: Option<ToolSignals>,
32    /// Algorithm-specific state keyed by stable internal names.
33    #[serde(flatten)]
34    pub extra: BTreeMap<String, StateValue>,
35}
36
37impl PartialEq for State {
38    fn eq(&self, other: &Self) -> bool {
39        self.extra == other.extra
40    }
41}
42
43#[cfg(test)]
44mod tests {
45    use super::*;
46
47    #[test]
48    fn state_round_trips_through_json_and_compares_extra_only() -> serde_json::Result<()> {
49        let mut state = State::default();
50        state
51            .extra
52            .insert("capable_hold_turns".to_string(), StateValue::Count(2));
53        state.extra.insert(
54            "fall_open".to_string(),
55            StateValue::String("capable".to_string()),
56        );
57        state
58            .extra
59            .insert("vgr_handoff_sent".to_string(), StateValue::Bool(true));
60        let json = serde_json::json!({
61            "capable_hold_turns": {"count": 2},
62            "fall_open": {"string": "capable"},
63            "vgr_handoff_sent": {"bool": true},
64        });
65
66        let with_signals = State {
67            tool_signals: Some(ToolSignals::default()),
68            ..state.clone()
69        };
70        assert_eq!(serde_json::to_value(&with_signals)?, json);
71        assert_eq!(serde_json::from_value::<State>(json)?, state);
72        assert_eq!(with_signals, state);
73        Ok(())
74    }
75}