use serde_json::Value;
pub fn build_session_context(
identity: &str,
team: &str,
repo_name: Option<&str>,
repo_root: Option<&str>,
branch: Option<&str>,
cwd: &str,
) -> String {
let repo_name_str = repo_name.unwrap_or("null");
let repo_root_str = repo_root.unwrap_or("null");
let branch_str = branch.unwrap_or("null");
format!(
"<session-context>\nIdentity: {identity}\nTeam: {team}\nRepo: {repo_name_str} ({repo_root_str})\nBranch: {branch_str}\nCWD: {cwd}\n</session-context>"
)
}
pub fn inject_developer_instructions(params: &mut Value, context: &str) {
let Some(obj) = params.as_object_mut() else {
return;
};
if let Some(existing) = obj.get_mut("developer-instructions") {
if let Some(s) = existing.as_str() {
let appended = format!("{s}\n{context}");
*existing = Value::String(appended);
} else {
*existing = Value::String(context.to_string());
}
} else {
obj.insert(
"developer-instructions".to_string(),
Value::String(context.to_string()),
);
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn build_session_context_formats_correctly() {
let ctx = build_session_context(
"arch-ctm",
"atm-dev",
Some("agent-team-mail"),
Some("/home/user/agent-team-mail"),
Some("develop"),
"/home/user/agent-team-mail",
);
assert!(ctx.contains("<session-context>"), "missing opening tag");
assert!(ctx.contains("</session-context>"), "missing closing tag");
assert!(ctx.contains("Identity: arch-ctm"));
assert!(ctx.contains("Team: atm-dev"));
assert!(ctx.contains("Repo: agent-team-mail (/home/user/agent-team-mail)"));
assert!(ctx.contains("Branch: develop"));
assert!(ctx.contains("CWD: /home/user/agent-team-mail"));
}
#[test]
fn build_session_context_null_repo_fields_when_not_in_git() {
let ctx = build_session_context(
"dev-agent",
"atm-dev",
None,
None,
None,
"/tmp/workspace",
);
assert!(ctx.contains("Repo: null (null)"));
assert!(ctx.contains("Branch: null"));
}
#[test]
fn inject_null_repo_fields_when_not_in_git() {
let ctx = build_session_context("dev", "team", None, None, None, "/tmp");
let mut params = json!({});
inject_developer_instructions(&mut params, &ctx);
let di = params["developer-instructions"].as_str().unwrap();
assert!(di.contains("null (null)"));
}
#[test]
fn inject_sets_when_no_existing_developer_instructions() {
let mut params = json!({"prompt": "hello"});
inject_developer_instructions(&mut params, "ctx-block");
assert_eq!(params["developer-instructions"], "ctx-block");
}
#[test]
fn inject_appends_to_existing_developer_instructions() {
let mut params = json!({"developer-instructions": "original"});
inject_developer_instructions(&mut params, "appended");
let di = params["developer-instructions"].as_str().unwrap();
assert_eq!(di, "original\nappended");
}
#[test]
fn inject_does_not_modify_base_instructions() {
let mut params = json!({
"base-instructions": "do not touch me",
"prompt": "hello"
});
inject_developer_instructions(&mut params, "ctx");
assert_eq!(params["base-instructions"], "do not touch me");
assert_eq!(params["developer-instructions"], "ctx");
}
#[test]
fn inject_on_non_object_does_nothing() {
let mut params = json!("not an object");
inject_developer_instructions(&mut params, "ctx");
assert_eq!(params, json!("not an object"));
}
#[test]
fn inject_on_null_params_does_nothing() {
let mut params = json!(null);
inject_developer_instructions(&mut params, "ctx");
assert_eq!(params, json!(null));
}
#[test]
fn inject_replaces_non_string_developer_instructions() {
let mut params = json!({"developer-instructions": 42});
inject_developer_instructions(&mut params, "ctx");
assert_eq!(params["developer-instructions"], "ctx");
}
#[test]
fn inject_multiple_times_appends_each_time() {
let mut params = json!({});
inject_developer_instructions(&mut params, "first");
inject_developer_instructions(&mut params, "second");
inject_developer_instructions(&mut params, "third");
let di = params["developer-instructions"].as_str().unwrap();
assert_eq!(di, "first\nsecond\nthird");
}
}