use std::sync::Arc;
use ikigai_core::EndpointSpace;
use ikigai_sparql::Store;
use crate::config::BrowseSettings;
pub struct Browse {
pub space: EndpointSpace,
pub store: Arc<Store>,
}
pub fn wire(settings: &BrowseSettings) -> Browse {
let store = Arc::new(Store::open(&settings.store).unwrap_or_else(|e| {
panic!(
"ikigai-dev: browse store `{}` cannot open: {e} — refusing to run with an \
in-memory archive (explanations and annotations would be silently lost). \
ONE process holds the store at a time; if another ikigai holds this lock \
(e.g. a main-host serve instance with `serve.browse.root` lines), move \
browse ownership HERE: drop those lines and prefer-mount this server \
instead — mount = \"prefer urn:repo:=<this socket>\" and \
mount = \"prefer urn:annotation:=<this socket>\". Otherwise fix the \
path/permissions.",
settings.store.display()
)
}));
ikigai_sparql::load_vocabulary(&store).unwrap_or_else(|e| {
panic!("ikigai-dev: loading the vocabulary into the browse store: {e:?}")
});
let explain = explain_config(settings, &store);
Browse {
space: ikigai_browse::Mount::new(settings.roots.clone())
.explain(explain)
.app("dev-server")
.space(),
store,
}
}
fn explain_config(settings: &BrowseSettings, store: &Arc<Store>) -> ikigai_browse::ExplainConfig {
let mut explain = ikigai_browse::ExplainConfig::new(Arc::clone(store))
.file_provider(settings.file_model.clone())
.dir_provider(settings.dir_model.clone())
.review_provider(settings.review_model.clone())
.pr_provider(settings.pr_model.clone())
.allow_providers(settings.allow_models.iter().cloned());
if let Some(tokens) = settings.file_max_tokens {
explain = explain.file_max_tokens(tokens);
}
if let Some(tokens) = settings.dir_max_tokens {
explain = explain.dir_max_tokens(tokens);
}
if let Some(tokens) = settings.review_max_tokens {
explain = explain.review_max_tokens(tokens);
}
if let Some(tokens) = settings.pr_max_tokens {
explain = explain.pr_max_tokens(tokens);
}
if let Some(label) = &settings.review_model_label {
explain = explain.review_model_label(label.clone());
}
if let Some(label) = &settings.pr_model_label {
explain = explain.pr_model_label(label.clone());
}
explain
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use std::sync::Arc;
use ikigai_core::{Iri, Request, Resolution, Scope, Space, Verb};
use ikigai_sparql::Store;
use super::explain_config;
use crate::config::BrowseSettings;
const FILE_TIER: &str = "urn:llm:coder:ask";
const DIR_TIER: &str = "urn:llm:ask";
const REVIEW_PROVIDER: &str = "urn:llm:review-only:ask";
const PR_PROVIDER: &str = "urn:llm:pr-only:ask";
fn settings(root: PathBuf, allow: &[&str]) -> BrowseSettings {
BrowseSettings {
roots: vec![("demo".to_string(), root)],
store: PathBuf::new(),
file_model: FILE_TIER.to_string(),
dir_model: DIR_TIER.to_string(),
review_model: REVIEW_PROVIDER.to_string(),
pr_model: PR_PROVIDER.to_string(),
file_max_tokens: None,
dir_max_tokens: None,
review_max_tokens: None,
pr_max_tokens: None,
review_model_label: None,
pr_model_label: None,
allow_models: allow.iter().map(|id| (*id).to_string()).collect(),
}
}
fn offered(allow: &[&str]) -> Vec<String> {
let root = std::env::temp_dir().join("ikigai-dev-menu-test");
std::fs::create_dir_all(&root).expect("temp root");
let settings = settings(root, allow);
let store = Arc::new(Store::new().expect("in-memory store"));
let space = ikigai_browse::Mount::new(settings.roots.clone())
.explain(explain_config(&settings, &store))
.app("dev-server")
.space();
let request = Request::new(
Verb::Source,
Iri::parse("urn:repo:demo:explain:src/main.rs").expect("iri"),
);
let Resolution::Hit(hit) = space.resolve(&request, &Scope::empty()) else {
panic!("the explain grammar no longer matches urn:repo:demo:explain:{{path}}")
};
hit.endpoint
.describe()
.inputs
.iter()
.find(|input| input.name == "provider")
.expect("explain publishes a `provider` ArgSpec")
.one_of
.clone()
}
#[test]
fn default_offers_only_the_two_tiers() {
let set = offered(&[]);
assert_eq!(set, [DIR_TIER, FILE_TIER]);
assert!(!set.contains(&REVIEW_PROVIDER.to_string()), "{set:?}");
assert!(!set.contains(&PR_PROVIDER.to_string()), "{set:?}");
}
#[test]
fn allowed_models_widen_the_set_and_nothing_else_does() {
let set = offered(&["urn:llm:big:ask", "urn:llm:mlx:ask"]);
assert_eq!(
set,
[
"urn:llm:ask",
"urn:llm:big:ask",
"urn:llm:coder:ask",
"urn:llm:mlx:ask"
]
);
assert!(!set.contains(&"urn:llm:ollama:ask".to_string()), "{set:?}");
}
}