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, HashMap};
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    /// Per-model prompts for this routing outcome, cleared after routing.
33    #[serde(skip)]
34    pub target_prompts: HashMap<switchyard_protocol::ModelId, String>,
35    /// Algorithm-specific state keyed by stable internal names.
36    #[serde(flatten)]
37    pub extra: BTreeMap<String, StateValue>,
38}
39
40impl PartialEq for State {
41    fn eq(&self, other: &Self) -> bool {
42        self.extra == other.extra
43    }
44}
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49
50    #[test]
51    fn state_round_trips_through_json_and_compares_extra_only() -> serde_json::Result<()> {
52        let mut state = State::default();
53        state
54            .extra
55            .insert("capable_hold_turns".to_string(), StateValue::Count(2));
56        state.extra.insert(
57            "fall_open".to_string(),
58            StateValue::String("capable".to_string()),
59        );
60        state
61            .extra
62            .insert("vgr_handoff_sent".to_string(), StateValue::Bool(true));
63        let json = serde_json::json!({
64            "capable_hold_turns": {"count": 2},
65            "fall_open": {"string": "capable"},
66            "vgr_handoff_sent": {"bool": true},
67        });
68
69        let with_signals = State {
70            tool_signals: Some(ToolSignals::default()),
71            ..state.clone()
72        };
73        assert_eq!(serde_json::to_value(&with_signals)?, json);
74        assert_eq!(serde_json::from_value::<State>(json)?, state);
75        assert_eq!(with_signals, state);
76        Ok(())
77    }
78}