use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use super::PrepError;
use crate::core::session_profile::{SUPERVISOR_MODEL, SessionProfile};
const RECORD_FILE: &str = "tm-written-model.json";
const MODEL_KEY: &str = "model";
type Record = BTreeMap<String, String>;
fn record_path(project_dir: &Path) -> PathBuf {
crate::core::harness_root::harness_dir(project_dir).join(RECORD_FILE)
}
fn settings_key(project_dir: &Path) -> String {
project_dir
.join(".claude")
.join("settings.json")
.display()
.to_string()
}
fn load_record(path: &Path) -> Record {
match std::fs::read_to_string(path) {
Ok(raw) => serde_json::from_str(&raw).unwrap_or_else(|err| {
tracing::warn!(path = %path.display(), "ignoring a malformed model record: {err}");
Record::new()
}),
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Record::new(),
Err(err) => {
tracing::warn!(path = %path.display(), "cannot read the model record: {err}");
Record::new()
}
}
}
fn store_record(path: &Path, record: &Record) -> Result<(), PrepError> {
let io = |source| PrepError::Io {
path: path.to_path_buf(),
source,
};
if record.is_empty() {
return match std::fs::remove_file(path) {
Err(err) if err.kind() != std::io::ErrorKind::NotFound => Err(io(err)),
_ => Ok(()),
};
}
if let Some(dir) = path.parent() {
std::fs::create_dir_all(dir).map_err(io)?;
}
let body = serde_json::to_string_pretty(record).map_err(|e| io(std::io::Error::other(e)))?;
std::fs::write(path, body).map_err(io)
}
pub(super) fn write_supervisor_model(
project_dir: &Path,
profile: SessionProfile,
) -> Result<(), PrepError> {
let path = record_path(project_dir);
let mut record = load_record(&path);
let key = settings_key(project_dir);
let owned = record.get(&key).cloned();
if profile.is_supervisor() {
let mut wrote = false;
super::settings::merge_settings(project_dir, |settings| {
let ours = match settings.get(MODEL_KEY) {
None => true,
Some(current) => owned
.as_deref()
.is_some_and(|v| current.as_str() == Some(v)),
};
if ours {
settings[MODEL_KEY] = serde_json::Value::from(SUPERVISOR_MODEL);
wrote = true;
}
})?;
if wrote {
record.insert(key, SUPERVISOR_MODEL.to_owned());
} else {
tracing::warn!(
project = %project_dir.display(),
"the project settings set their own `model`; the supervisor model \
`{SUPERVISOR_MODEL}` is passed on the command line only"
);
record.remove(&key);
}
return store_record(&path, &record);
}
let Some(owned) = owned else {
return Ok(());
};
super::settings::merge_settings(project_dir, |settings| {
if settings.get(MODEL_KEY).and_then(serde_json::Value::as_str) == Some(owned.as_str())
&& let Some(map) = settings.as_object_mut()
{
map.remove(MODEL_KEY);
}
})?;
record.remove(&key);
store_record(&path, &record)
}