use agentplane::core::{Label, SourceId, Tainted};
use serde_json::Value;
pub const VERIFIER: &str = "speccoder://verifier";
pub const WORKTREE: &str = "speccoder://worktree";
pub const PERSON: &str = "speccoder://person";
#[must_use]
pub fn tool(server: &str, name: &str) -> SourceId {
agentplane::tools::ToolId::new(server, name).source()
}
#[must_use]
pub fn from(source: &str) -> Label {
Label::untrusted(SourceId::new(source))
}
#[must_use]
pub fn labelled_prompt(prompt: Value, label: &Label) -> Tainted<Value> {
let Value::Object(parts) = prompt else {
return Tainted::with_label(prompt, label.clone());
};
Tainted::object(parts.into_iter().map(|(k, v)| {
let part = if k == "system" {
Tainted::trusted(v)
} else {
Tainted::with_label(v, label.clone())
};
(k, part)
}))
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn the_instruction_stays_trusted_and_the_rest_carries_its_sources() {
let p = labelled_prompt(
json!({ "system": "be careful", "messages": [{ "role": "user", "content": "x" }] }),
&from(VERIFIER),
);
assert!(!p.label_at("/system").is_some_and(Label::is_untrusted));
assert!(p.label_at("/messages").is_some_and(Label::is_untrusted));
assert!(p.label().is_untrusted());
}
}