Skip to main content

siumai_bridge/
planner.rs

1//! Bridge planning helpers.
2//!
3//! The planner decides which high-level path a bridge should take. It does not
4//! perform protocol serialization itself.
5
6use crate::BridgeTarget;
7
8use super::request::pairs::{DirectRequestBridgePair, direct_request_bridge_pair};
9
10/// High-level path selected for a request bridge.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum RequestBridgePath {
13    ViaNormalized,
14    Direct(DirectRequestBridgePair),
15}
16
17/// A request bridge plan selected from the requested source/target pair.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub struct RequestBridgePlan {
20    pub source: Option<BridgeTarget>,
21    pub target: BridgeTarget,
22    pub path: RequestBridgePath,
23}
24
25impl RequestBridgePlan {
26    pub const fn uses_direct_pair_bridge(&self) -> bool {
27        matches!(self.path, RequestBridgePath::Direct(_))
28    }
29}
30
31/// Plan how a request bridge should be executed.
32///
33/// The current policy is intentionally conservative:
34///
35/// - select a direct bridge only for the highest-value pairwise paths
36/// - otherwise fall back to the normalized bridge path
37pub fn plan_chat_request_bridge(
38    source: Option<BridgeTarget>,
39    target: BridgeTarget,
40) -> RequestBridgePlan {
41    let path = match source.and_then(|source| direct_request_bridge_pair(source, target)) {
42        Some(pair) => RequestBridgePath::Direct(pair),
43        None => RequestBridgePath::ViaNormalized,
44    };
45
46    RequestBridgePlan {
47        source,
48        target,
49        path,
50    }
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56    use crate::request::pairs::DirectRequestBridgePair;
57
58    #[test]
59    fn planner_selects_direct_pair_for_anthropic_to_openai_responses() {
60        let plan = plan_chat_request_bridge(
61            Some(BridgeTarget::AnthropicMessages),
62            BridgeTarget::OpenAiResponses,
63        );
64
65        assert_eq!(
66            plan.path,
67            RequestBridgePath::Direct(DirectRequestBridgePair::AnthropicMessagesToOpenAiResponses,)
68        );
69    }
70
71    #[test]
72    fn planner_selects_direct_pair_for_openai_responses_to_anthropic() {
73        let plan = plan_chat_request_bridge(
74            Some(BridgeTarget::OpenAiResponses),
75            BridgeTarget::AnthropicMessages,
76        );
77
78        assert_eq!(
79            plan.path,
80            RequestBridgePath::Direct(DirectRequestBridgePair::OpenAiResponsesToAnthropicMessages,)
81        );
82    }
83
84    #[test]
85    fn planner_uses_normalized_path_for_non_direct_target_pair() {
86        let plan = plan_chat_request_bridge(
87            Some(BridgeTarget::AnthropicMessages),
88            BridgeTarget::OpenAiChatCompletions,
89        );
90
91        assert_eq!(plan.path, RequestBridgePath::ViaNormalized);
92    }
93}