use std::panic::{catch_unwind, AssertUnwindSafe};
use rpi_plugin_sdk::{StbString, StbStringRef};
use crate::registry::{assert_active, BeforeAgentStartHandler, RegistrySnapshot};
pub fn emit_before_agent_start(
current_prompt: &str,
event_json: &str,
snapshot: &RegistrySnapshot,
) -> Option<String> {
if !assert_active(snapshot.active_flag()) {
return None;
}
let handlers = snapshot.before_agent_start();
if handlers.is_empty() {
return None;
}
let mut prompt = current_prompt.to_string();
let mut changed = false;
for h in handlers {
let envelope = with_system_prompt(event_json, &prompt);
let envelope_ref = StbStringRef::from_str(&envelope);
let outcome = catch_unwind(AssertUnwindSafe(|| call_one_handler(*h, envelope_ref)));
match outcome {
Ok(Ok(Some(next))) => {
prompt = next;
changed = true;
}
Ok(Ok(None)) => {}
Ok(Err(rc)) => {
tracing::warn!(
rc,
"before_agent_start handler returned nonzero — prompt left as-is \
(fan-out continues)"
);
}
Err(_) => {
tracing::error!(
"before_agent_start handler panicked — prompt left as-is \
(fan-out continues)"
);
}
}
}
changed.then_some(prompt)
}
fn with_system_prompt(event_json: &str, prompt: &str) -> String {
match serde_json::from_str::<serde_json::Value>(event_json) {
Ok(serde_json::Value::Object(mut map)) => {
map.insert(
"systemPrompt".to_string(),
serde_json::Value::String(prompt.to_string()),
);
serde_json::Value::Object(map).to_string()
}
_ => serde_json::json!({ "systemPrompt": prompt }).to_string(),
}
}
fn call_one_handler(
h: BeforeAgentStartHandler,
event_ref: StbStringRef,
) -> Result<Option<String>, i32> {
let mut out = StbString::empty();
let rc = (h.handler)(event_ref, &mut out, h.user_data);
if rc != 0 {
out.free_with(Some(h.plugin_free_string));
return Err(rc);
}
let json = out.to_string_lossy();
out.free_with(Some(h.plugin_free_string));
Ok(parse_prompt_payload(&json))
}
fn parse_prompt_payload(json: &str) -> Option<String> {
let trimmed = json.trim();
if trimmed.is_empty() {
return None;
}
let value: serde_json::Value = serde_json::from_str(trimmed).ok()?;
value.get("systemPrompt")?.as_str().map(str::to_string)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_takes_the_string_and_ignores_everything_else() {
assert_eq!(
parse_prompt_payload(r#"{"systemPrompt":"hi"}"#),
Some("hi".to_string())
);
assert_eq!(
parse_prompt_payload(r#"{"systemPrompt":""}"#),
Some(String::new())
);
assert_eq!(parse_prompt_payload("{}"), None);
assert_eq!(parse_prompt_payload(r#"{"systemPrompt":42}"#), None);
assert_eq!(parse_prompt_payload("not json"), None);
assert_eq!(parse_prompt_payload(""), None);
assert_eq!(parse_prompt_payload(" "), None);
}
#[test]
fn envelope_carries_the_prompt_and_survives_a_bad_base() {
let base = serde_json::json!({"prompt": "do a thing", "imageCount": 0}).to_string();
let out = with_system_prompt(&base, "you are terse");
let v: serde_json::Value = serde_json::from_str(&out).unwrap();
assert_eq!(v["prompt"], "do a thing");
assert_eq!(v["systemPrompt"], "you are terse");
let out = with_system_prompt("[]", "p");
let v: serde_json::Value = serde_json::from_str(&out).unwrap();
assert_eq!(v["systemPrompt"], "p");
let second = with_system_prompt(&out, "updated");
let v2: serde_json::Value = serde_json::from_str(&second).unwrap();
assert_eq!(v2["systemPrompt"], "updated");
}
}