use super::*;
pub fn write_backend_dropin(
config_path: &std::path::Path,
backend: &BackendConfig,
) -> std::result::Result<std::path::PathBuf, String> {
let config_destination = crate::atomic_fs::ResolvedPath::resolve(config_path)
.map_err(|error| format!("resolve config destination: {error:#}"))?;
let _lock = crate::atomic_fs::acquire_lock(&config_destination.lock_path())
.map_err(|error| format!("lock {}: {error:#}", config_path.display()))?;
write_backend_dropin_unlocked(config_path, backend)
}
fn write_backend_dropin_unlocked(
config_path: &std::path::Path,
backend: &BackendConfig,
) -> std::result::Result<std::path::PathBuf, String> {
if backend.name.trim().is_empty() {
return Err("backend drop-in needs a name (it becomes the filename)".into());
}
let dir = config_path.with_file_name("backends");
std::fs::create_dir_all(&dir).map_err(|e| format!("create {}: {e}", dir.display()))?;
let path = dir.join(format!("{}.toml", backend.name));
let destination = crate::atomic_fs::ResolvedPath::resolve(&path)
.map_err(|e| format!("resolve {}: {e:#}", path.display()))?;
let body = render_operator_backend_dropin(backend)?;
destination
.atomic_write(body.as_bytes())
.map_err(|e| format!("write {}: {e:#}", path.display()))?;
Ok(path)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum DropinOwnership {
Operator,
Probe,
}
pub fn classify_backend_dropin(text: &str) -> std::result::Result<DropinOwnership, String> {
match disk_record_tag(text)? {
Some(RecordTag::ProbeV1) => Ok(DropinOwnership::Probe),
Some(RecordTag::OperatorV1) => Ok(DropinOwnership::Operator),
None => {
let backend = toml::from_str::<BackendConfig>(text).map_err(|e| e.to_string())?;
match classify_untagged_dropin(&backend, text)? {
DropinOwner::Operator => Ok(DropinOwnership::Operator),
DropinOwner::Probe => Ok(DropinOwnership::Probe),
}
}
}
}
pub fn render_operator_backend_dropin(
backend: &BackendConfig,
) -> std::result::Result<String, String> {
let serialized = toml::to_string(backend).map_err(|e| format!("serialize backend: {e}"))?;
Ok(format!("record = \"operator_v1\"\n{serialized}"))
}
pub fn claim_backend_dropin_as_operator(text: &str) -> std::result::Result<String, String> {
let mut doc = text
.parse::<toml_edit::DocumentMut>()
.map_err(|e| format!("backend drop-in is not valid TOML: {e}"))?;
let root = doc.as_table_mut();
match root.get_mut("record") {
Some(item) if item.is_value() => {
let value = item.as_value_mut().expect("checked is_value");
let decor = value.decor().clone();
*value = toml_edit::Value::from("operator_v1");
*value.decor_mut() = decor;
}
Some(_) => {
return Err(
"this drop-in has a `[record]` table/array where the ownership tag \
would go — refusing to overwrite it; rename or remove that table \
first, then claim the file"
.to_string(),
);
}
None => {
root.insert("record", toml_edit::value("operator_v1"));
}
}
Ok(doc.to_string())
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ProbeObservation {
pub name: String,
pub endpoint: String,
pub kind: Option<BackendKind>,
pub api: Option<OpenAiApi>,
pub serving: ProbedServing,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ProbedServing {
Instance { model: Option<String> },
Multiplexer,
Unknown,
}
impl ProbeObservation {
#[must_use]
pub fn serving_axis(&self) -> (Option<Serving>, Option<String>) {
match &self.serving {
ProbedServing::Instance { model } => (Some(Serving::Instance), model.clone()),
ProbedServing::Multiplexer => (Some(Serving::Multiplexer), None),
ProbedServing::Unknown => (None, None),
}
}
}
pub(super) fn probe_machine_record(observation: &ProbeObservation) -> ProbeRecordV1 {
let (serving, model) = observation.serving_axis();
ProbeRecordV1 {
name: Some(observation.name.clone()),
endpoint: observation.endpoint.clone(),
kind: observation.kind,
api: observation.api,
serving,
model,
tiers: Vec::new(),
record: Some(RecordTag::ProbeV1),
provenance: Some(ProbeProvenanceV1 {
source: Some(format!(
"newt adopt v{} (probe_v1 overlay; delete this file to reset)",
crate::build_info::VERSION_WITH_COMMIT
)),
probed: Some(chrono::Local::now().format("%Y-%m-%d").to_string()),
derived_serving: serving.map(|_| true),
}),
}
}
#[derive(Debug)]
pub(super) enum DropinOwner {
Operator,
Probe,
}
fn is_legacy_adopt_probe_marker(source: &str) -> bool {
source
.strip_prefix("newt adopt v")
.and_then(|rest| rest.strip_suffix(" (probed; delete this file to reset)"))
.is_some_and(|version| !version.is_empty())
}
pub(super) fn classify_untagged_dropin(
b: &BackendConfig,
text: &str,
) -> std::result::Result<DropinOwner, String> {
let source = b
.provenance
.as_ref()
.and_then(|p| p.source.as_deref())
.unwrap_or("");
if !is_legacy_adopt_probe_marker(source) {
return Ok(DropinOwner::Operator);
}
let strict = toml::from_str::<ProbeRecordV1>(text);
if let Ok(record) = &strict {
if record.model.is_none() && record.tiers.is_empty() {
return Ok(DropinOwner::Probe);
}
}
let carried = match (b.model.is_some(), b.card.is_some(), strict.is_err()) {
(true, true, _) => "a model and a card",
(true, false, _) => "a model",
(false, true, _) => "a card",
(false, false, true) => "keys outside the old probe cache's raw shape",
(false, false, false) => "operator fields beside the probe marker",
};
Err(format!(
"this backend drop-in carries the old newt-adopt probe marker but also \
{carried} — written by an older newt, its declarations cannot be \
attributed: as an operator record (A) they replace the configured \
backend wholesale; as a probe overlay (B) they are per-session residue \
that must be discarded. Refusing to guess — delete the file to \
re-probe, or add `record = \"operator_v1\"` to claim it as \
configuration."
))
}
fn validate_probe_record(r: &ProbeRecordV1) -> std::result::Result<(), String> {
if r.endpoint.trim().is_empty() {
return Err("probe record has no endpoint (the association key)".to_string());
}
if r.model.is_some() && r.serving != Some(Serving::Instance) {
return Err(
"probe record carries a model without serving = \"instance\" — only an \
instance's model is backend truth"
.to_string(),
);
}
if !r.tiers.is_empty() {
return Err(
"probe record carries operator-owned field `tiers` — a probe overlay may \
hold only endpoint/kind/api/serving (plus an instance's model)"
.to_string(),
);
}
Ok(())
}
#[derive(Deserialize)]
struct DiskRecordHeader {
#[serde(default)]
record: Option<RecordTag>,
}
pub(super) fn disk_record_tag(text: &str) -> std::result::Result<Option<RecordTag>, String> {
toml::from_str::<DiskRecordHeader>(text)
.map(|h| h.record)
.map_err(|e| e.to_string())
}
#[derive(Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub(super) struct ProbeRecordV1 {
#[serde(default, skip_serializing_if = "Option::is_none")]
name: Option<String>,
#[serde(default)]
endpoint: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
kind: Option<BackendKind>,
#[serde(default, skip_serializing_if = "Option::is_none")]
api: Option<OpenAiApi>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(super) serving: Option<Serving>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(super) model: Option<String>,
#[serde(default, skip_serializing)]
tiers: Vec<Tier>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(super) record: Option<RecordTag>,
#[serde(default, skip_serializing_if = "Option::is_none")]
provenance: Option<ProbeProvenanceV1>,
}
#[derive(Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
struct ProbeProvenanceV1 {
#[serde(default, skip_serializing_if = "Option::is_none")]
source: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
probed: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
derived_serving: Option<bool>,
}
pub(super) fn parse_probe_record(text: &str) -> std::result::Result<ProbeRecordV1, String> {
let r: ProbeRecordV1 =
toml::from_str(text).map_err(|e| format!("not a valid probe record: {e}"))?;
validate_probe_record(&r).map(|()| r)
}
impl ProbeRecordV1 {
pub(super) fn to_observation(&self, name: &str) -> ProbeObservation {
ProbeObservation {
name: name.to_string(),
endpoint: self.endpoint.clone(),
kind: self.kind,
api: self.api,
serving: match (self.serving, &self.model) {
(Some(Serving::Instance), model) => ProbedServing::Instance {
model: model.clone(),
},
(Some(Serving::Multiplexer), _) => ProbedServing::Multiplexer,
(None, _) => ProbedServing::Unknown,
},
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ProbeWriteback {
Written(std::path::PathBuf),
SkippedOperatorOwned(std::path::PathBuf),
NotWritten,
}
pub fn persist_probe_observation(
observation: &ProbeObservation,
) -> std::result::Result<ProbeWriteback, String> {
if observation.name.trim().is_empty() {
return Ok(ProbeWriteback::NotWritten);
}
let Some(config_path) = Config::user_config_path() else {
return Ok(ProbeWriteback::NotWritten);
};
let config_destination = crate::atomic_fs::ResolvedPath::resolve(&config_path)
.map_err(|error| format!("resolve config destination: {error:#}"))?;
let _lock = crate::atomic_fs::acquire_lock(&config_destination.lock_path())
.map_err(|error| format!("lock {}: {error:#}", config_path.display()))?;
let dir = config_path.with_file_name("backends");
let path = dir.join(format!("{}.toml", observation.name));
let destination = crate::atomic_fs::ResolvedPath::resolve(&path)
.map_err(|e| format!("resolve {}: {e:#}", path.display()))?;
let mut merged = probe_machine_record(observation);
if destination.as_path().is_file() {
let text = std::fs::read_to_string(destination.as_path())
.map_err(|e| format!("read {}: {e}", path.display()))?;
let owned_by_probe =
match disk_record_tag(&text).map_err(|e| format!("parse {}: {e}", path.display()))? {
Some(RecordTag::ProbeV1) => true,
Some(RecordTag::OperatorV1) => false,
None => {
let prior = toml::from_str::<BackendConfig>(&text)
.map_err(|e| format!("parse {}: {e}", path.display()))?;
match classify_untagged_dropin(&prior, &text) {
Ok(DropinOwner::Probe) => true,
Ok(DropinOwner::Operator) => false,
Err(reason) => return Err(format!("{}: {reason}", path.display())),
}
}
};
if !owned_by_probe {
return Ok(ProbeWriteback::SkippedOperatorOwned(path));
}
let prior = parse_probe_record(&text).map_err(|e| {
format!(
"{}: existing probe record is invalid ({e}) — delete it to re-probe",
path.display()
)
})?;
if prior.endpoint == observation.endpoint {
merged.kind = merged.kind.or(prior.kind);
merged.api = merged.api.or(prior.api);
}
}
validate_probe_record(&merged)
.map_err(|e| format!("refusing to write an invalid probe record: {e}"))?;
let body = toml::to_string(&merged).map_err(|e| format!("serialize probe record: {e}"))?;
destination
.atomic_write(body.as_bytes())
.map_err(|e| format!("write {}: {e:#}", path.display()))?;
Ok(ProbeWriteback::Written(path))
}
#[deprecated(note = "use persist_probe_observation — probe persistence is typed (#1819)")]
pub fn writeback_probed_backend(
patch: &BackendConfig,
) -> std::result::Result<Option<std::path::PathBuf>, String> {
if patch.model.is_some() && patch.serving != Some(Serving::Instance) {
return Err(
"probe writeback carries a model without serving = \"instance\" — only an \
instance's model is backend truth; use persist_probe_observation"
.to_string(),
);
}
let operator_owned: &[(&str, bool)] = &[
("card", patch.card.is_some()),
("capability", patch.capability.is_some()),
("api_key_env", patch.api_key_env.is_some()),
("api_key_file", patch.api_key_file.is_some()),
("managed", patch.managed.is_some()),
("host", patch.host.is_some()),
("coexist", patch.coexist.is_some()),
("ram_gib", patch.ram_gib.is_some()),
("engine", patch.engine.is_some()),
("model_path", patch.model_path.is_some()),
("tiers", !patch.tiers.is_empty()),
];
if let Some((field, _)) = operator_owned.iter().find(|(_, present)| *present) {
return Err(format!(
"probe writeback carries operator-owned field `{field}` — a probe record may \
hold only endpoint/kind/api/serving (plus an instance's model); use \
write_backend_dropin for operator configuration"
));
}
let serving = match patch.serving {
Some(Serving::Instance) => ProbedServing::Instance {
model: patch.model.clone(),
},
Some(Serving::Multiplexer) => ProbedServing::Multiplexer,
None => ProbedServing::Unknown,
};
let observation = ProbeObservation {
name: patch.name.clone(),
endpoint: patch.endpoint.clone(),
kind: patch.kind,
api: patch.api,
serving,
};
match persist_probe_observation(&observation)? {
ProbeWriteback::Written(path) => Ok(Some(path)),
ProbeWriteback::SkippedOperatorOwned(path) => Err(format!(
"{}: the same-name drop-in is operator-owned — the probe record was NOT \
written (delete the file, or keep it and stop probing this backend)",
path.display()
)),
ProbeWriteback::NotWritten => Ok(None),
}
}