1use omgbase_store::Store;
17use serde_json::{Map, Value as Json, json};
18
19pub const MCP_PROTOCOL_VERSION: &str = "2025-11-25";
25
26#[derive(Clone, Debug, PartialEq, Eq)]
28pub struct BuildInfo {
29 pub version: String,
31 pub cli_spec: Option<String>,
34 pub commit: Option<String>,
36 pub built: Option<String>,
38 pub rustc: Option<String>,
40}
41
42impl Default for BuildInfo {
43 fn default() -> Self {
45 Self {
46 version: crate::VERSION.to_owned(),
47 cli_spec: None,
48 commit: None,
49 built: None,
50 rustc: None,
51 }
52 }
53}
54
55fn components(build: &BuildInfo) -> Json {
59 let mut m: Vec<(&str, &str)> = vec![
60 ("omgbase", build.version.as_str()),
61 ("omgbase-format", omgbase_format::VERSION),
62 ("omgbase-graph", omgbase_graph::VERSION),
63 ("omgbase-mutate", omgbase_mutate::VERSION),
64 ("omgbase-properties", omgbase_properties::VERSION),
65 ("omgbase-reconcile", omgbase_reconcile::VERSION),
66 ("omgbase-search", omgbase_search::VERSION),
67 ("omgbase-store", omgbase_store::VERSION),
68 ("omgbase-surface", crate::VERSION),
69 ("omgbase-sync", omgbase_sync::VERSION),
70 ("oqx", oqx::VERSION),
71 ];
72 m.sort_by(|a, b| a.0.as_bytes().cmp(b.0.as_bytes()));
73 Json::Object(
74 m.into_iter()
75 .map(|(k, v)| (k.to_owned(), Json::String(v.to_owned())))
76 .collect::<Map<_, _>>(),
77 )
78}
79
80fn specs(build: &BuildInfo) -> Json {
82 json!({
83 "oqx": oqx::LANGUAGE_VERSION,
84 "format": omgbase_format::SPEC_VERSION,
85 "reconcile": omgbase_reconcile::SPEC_VERSION,
86 "store": omgbase_store::SPEC_VERSION,
87 "properties": omgbase_properties::SPEC_VERSION,
88 "graph": omgbase_graph::SPEC_VERSION,
89 "search": omgbase_search::SPEC_VERSION,
90 "mutate": omgbase_mutate::SPEC_VERSION,
91 "sync": omgbase_sync::SPEC_VERSION,
92 "surface": crate::SPEC_VERSION,
93 "cli": build.cli_spec,
94 })
95}
96
97pub fn version_info(store: Option<&Store>, build: &BuildInfo) -> crate::Result<Json> {
101 let schema = match store {
102 Some(s) => Json::from(s.user_version()?),
103 None => Json::Null,
104 };
105 let runtime = build.rustc.clone().unwrap_or_else(|| "rustc".to_owned());
106 Ok(json!({
107 "engine": "rust",
108 "version": build.version,
109 "components": components(build),
110 "specs": specs(build),
111 "schema": schema,
112 "mcp": { "protocol": MCP_PROTOCOL_VERSION },
113 "runtime": runtime,
114 "commit": build.commit,
115 "built": build.built,
116 }))
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
124 fn library_embedding_answers_with_its_own_version_and_nulls() {
125 let store = Store::open_in_memory().unwrap();
126 let v = version_info(Some(&store), &BuildInfo::default()).unwrap();
127 assert_eq!(v["engine"], "rust");
128 assert_eq!(v["version"], crate::VERSION);
129 assert_eq!(v["components"]["omgbase-surface"], crate::VERSION);
130 assert_eq!(v["components"]["omgbase"], crate::VERSION);
131 assert_eq!(v["components"]["oqx"], oqx::VERSION);
132 let keys: Vec<&String> = v["components"].as_object().unwrap().keys().collect();
133 let mut sorted = keys.clone();
134 sorted.sort_by(|a, b| a.as_bytes().cmp(b.as_bytes()));
135 assert_eq!(keys, sorted);
136 assert_eq!(keys.len(), 11);
137 assert_eq!(v["specs"]["surface"], crate::SPEC_VERSION);
138 assert_eq!(v["specs"]["store"], omgbase_store::SPEC_VERSION);
139 assert_eq!(v["specs"]["cli"], Json::Null);
140 assert_eq!(
141 v["specs"].as_object().unwrap().keys().collect::<Vec<_>>(),
142 [
143 "oqx",
144 "format",
145 "reconcile",
146 "store",
147 "properties",
148 "graph",
149 "search",
150 "mutate",
151 "sync",
152 "surface",
153 "cli"
154 ]
155 );
156 assert_eq!(v["schema"], omgbase_store::schema::SCHEMA_VERSION);
157 assert_eq!(v["mcp"], json!({ "protocol": MCP_PROTOCOL_VERSION }));
158 assert_eq!(v["runtime"], "rustc");
159 assert_eq!(v["commit"], Json::Null);
160 assert_eq!(v["built"], Json::Null);
161 assert_eq!(
162 v.as_object().unwrap().keys().collect::<Vec<_>>(),
163 [
164 "engine",
165 "version",
166 "components",
167 "specs",
168 "schema",
169 "mcp",
170 "runtime",
171 "commit",
172 "built"
173 ]
174 );
175 let none = version_info(None, &BuildInfo::default()).unwrap();
176 assert_eq!(none["schema"], Json::Null);
177 }
178
179 #[test]
180 fn a_host_supplies_its_own_identity() {
181 let build = BuildInfo {
182 version: "9.9.9".to_owned(),
183 cli_spec: Some("1.1".to_owned()),
184 commit: Some("abc1234".to_owned()),
185 built: Some("2026-09-27T00:00:00Z".to_owned()),
186 rustc: Some("rustc 1.97.0".to_owned()),
187 };
188 let v = version_info(None, &build).unwrap();
189 assert_eq!(v["version"], "9.9.9");
190 assert_eq!(v["components"]["omgbase"], "9.9.9");
191 assert_eq!(v["specs"]["cli"], "1.1");
192 assert_eq!(v["runtime"], "rustc 1.97.0");
193 assert_eq!(v["commit"], "abc1234");
194 assert_eq!(v["built"], "2026-09-27T00:00:00Z");
195 }
196}