chio_cross_protocol/
discovery.rs1use std::collections::BTreeMap;
2use std::fmt;
3
4use chio_manifest::ToolDefinition;
5use serde::{Deserialize, Serialize};
6
7use crate::execution::TargetProtocolExecutor;
8use crate::validation::schema_string_extension;
9
10pub struct TargetProtocolRegistry<'a> {
13 default_target_protocol: DiscoveryProtocol,
14 executors: BTreeMap<DiscoveryProtocol, &'a dyn TargetProtocolExecutor>,
15}
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
19#[serde(rename_all = "snake_case")]
20pub enum DiscoveryProtocol {
21 Native,
22 Http,
23 Mcp,
24 A2a,
25 Acp,
26 OpenAi,
27}
28
29impl DiscoveryProtocol {
30 #[must_use]
31 pub fn as_str(self) -> &'static str {
32 match self {
33 Self::Native => "native",
34 Self::Http => "http",
35 Self::Mcp => "mcp",
36 Self::A2a => "a2a",
37 Self::Acp => "acp",
38 Self::OpenAi => "open_ai",
39 }
40 }
41}
42
43impl fmt::Display for DiscoveryProtocol {
44 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45 f.write_str(self.as_str())
46 }
47}
48
49impl<'a> TargetProtocolRegistry<'a> {
50 #[must_use]
51 pub fn new(default_target_protocol: DiscoveryProtocol) -> Self {
52 Self {
53 default_target_protocol,
54 executors: BTreeMap::new(),
55 }
56 }
57
58 #[must_use]
59 pub fn with_executor(mut self, executor: &'a dyn TargetProtocolExecutor) -> Self {
60 self.executors.insert(executor.target_protocol(), executor);
61 self
62 }
63
64 #[must_use]
65 pub fn default_target_protocol(&self) -> DiscoveryProtocol {
66 self.default_target_protocol
67 }
68
69 pub fn resolve_target_protocol(
70 &self,
71 tool: &ToolDefinition,
72 ) -> Result<DiscoveryProtocol, String> {
73 let target = match schema_string_extension(&tool.input_schema, "x-chio-target-protocol")? {
74 Some(value) => Some(value),
75 None => match tool.output_schema.as_ref() {
76 Some(schema) => schema_string_extension(schema, "x-chio-target-protocol")?,
77 None => None,
78 },
79 };
80
81 match target {
82 Some(value) => parse_discovery_protocol(&value),
83 None => Ok(self.default_target_protocol),
84 }
85 }
86
87 #[must_use]
88 pub fn supports_target_protocol(&self, protocol: DiscoveryProtocol) -> bool {
89 protocol == DiscoveryProtocol::Native || self.executors.contains_key(&protocol)
90 }
91
92 pub(crate) fn executor_for_target(
93 &self,
94 protocol: DiscoveryProtocol,
95 ) -> Option<&'a dyn TargetProtocolExecutor> {
96 self.executors.get(&protocol).copied()
97 }
98}
99
100pub fn target_protocol_for_tool(tool: &ToolDefinition) -> Result<DiscoveryProtocol, String> {
105 TargetProtocolRegistry::new(DiscoveryProtocol::Native).resolve_target_protocol(tool)
106}
107
108pub fn target_protocol_for_tool_with_registry(
111 tool: &ToolDefinition,
112 registry: &TargetProtocolRegistry<'_>,
113) -> Result<DiscoveryProtocol, String> {
114 registry.resolve_target_protocol(tool)
115}
116
117pub fn parse_discovery_protocol(value: &str) -> Result<DiscoveryProtocol, String> {
119 let normalized = value.trim().to_ascii_lowercase();
120 match normalized.as_str() {
121 "native" => Ok(DiscoveryProtocol::Native),
122 "http" => Ok(DiscoveryProtocol::Http),
123 "mcp" => Ok(DiscoveryProtocol::Mcp),
124 "a2a" => Ok(DiscoveryProtocol::A2a),
125 "acp" => Ok(DiscoveryProtocol::Acp),
126 "open_ai" | "openai" => Ok(DiscoveryProtocol::OpenAi),
127 _ => Err(format!(
128 "unsupported x-chio-target-protocol value `{value}`; expected one of native, http, mcp, a2a, acp, open_ai"
129 )),
130 }
131}