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