use super::*;
fn record_subdir_instruction_load(
session_persistence: &mut SessionPersistence<'_>,
output_sink: &mut Option<&mut dyn AgentOutputSink>,
path: &std::path::Path,
bytes: usize,
activity_sender: Option<&ActivitySender>,
parent_activity_id: &str,
tool_call_id: &str,
) -> anyhow::Result<()> {
session_persistence.try_record(
SessionEventKind::SubdirInstructionLoad,
json!({
"source": "subdir_agents",
"path": path,
"status": "success",
"bytes": bytes,
"tool_call_id": tool_call_id,
}),
output_sink,
)?;
if let Some(sink) = output_sink.as_deref_mut() {
sink.output_event(OutputEvent::SubdirInstructionInjection {
path: path.to_path_buf(),
bytes,
parent_activity_id: Some(parent_activity_id.to_string()),
})?;
}
if let Some(sender) = activity_sender {
let mut hasher = std::collections::hash_map::DefaultHasher::new();
path.hash(&mut hasher);
let slug = format!("{:x}", hasher.finish());
let parent_id = ActivityId::new(parent_activity_id);
let id = parent_id.child(format!("subdir-instruction-injection-{slug}"));
let metadata = ActivityMetadata {
label: format!("Loaded subdirectory instructions: {}", path.display()),
detail: Some(format!(
"Loaded subdirectory instructions: {} ({} bytes)",
path.display(),
bytes
)),
fields: vec![
("source".to_string(), "subdir_agents".to_string()),
("path".to_string(), path.display().to_string()),
("status".to_string(), "success".to_string()),
("bytes".to_string(), bytes.to_string()),
],
};
sender(ActivityEvent::Started {
id: id.clone(),
parent_id: Some(parent_id),
kind: ActivityKind::ProviderContextInjection,
status: ActivityStatus::Running,
metadata: metadata.clone(),
});
sender(ActivityEvent::Finished {
id,
status: ActivityStatus::Success,
metadata: Some(metadata),
});
}
Ok(())
}
pub(in crate::agent) fn record_provider_context_item(
session_persistence: &mut SessionPersistence<'_>,
output_sink: &mut Option<&mut dyn AgentOutputSink>,
item: &ProviderConversationItem,
) -> anyhow::Result<()> {
let ProviderConversationItem::Message(message) = item else {
return Ok(());
};
session_persistence.try_record(
SessionEventKind::ProviderContextItem,
json!({"role": message.role.as_api_str(), "content": message.content}),
output_sink,
)
}
fn record_subdir_provider_context_item(
session_persistence: &mut SessionPersistence<'_>,
output_sink: &mut Option<&mut dyn AgentOutputSink>,
item: &ProviderConversationItem,
path: &std::path::Path,
) -> anyhow::Result<()> {
let ProviderConversationItem::Message(message) = item else {
return Ok(());
};
session_persistence.try_record(
SessionEventKind::ProviderContextItem,
json!({
"role": message.role.as_api_str(),
"content": message.content,
"source": "subdir_agents",
"path": path,
}),
output_sink,
)
}
#[expect(clippy::too_many_arguments)]
pub(super) fn discover_and_record_subdir_context_items(
tools: Option<&ToolRuntime>,
session_persistence: &mut SessionPersistence<'_>,
output_sink: &mut Option<&mut dyn AgentOutputSink>,
subdir_instruction_state: Option<&mut super::subdir_instructions::SubdirInstructionState>,
touched_paths: &[TouchedPath],
activity_sender: Option<&ActivitySender>,
parent_activity_id: Option<&str>,
tool_call_id: &str,
) -> anyhow::Result<Vec<ProviderConversationItem>> {
let Some(tools) = tools.filter(|tools| tools.subdir_discovery_enabled()) else {
return Ok(Vec::new());
};
let Some(state) = subdir_instruction_state else {
return Ok(Vec::new());
};
let Some(parent_activity_id) = parent_activity_id else {
return Ok(Vec::new());
};
let root = tools.cwd_canonical();
let claimed = state.claimed_mut();
let mut subdir_context_items = Vec::new();
for touched in touched_paths {
if !touched.inside_root || touched.self_authored_agents_md {
continue;
}
let (discovered, diagnostics) =
discover_subdir_instructions(&touched.canonical, root, claimed);
for message in &diagnostics {
if let Some(sink) = output_sink.as_deref_mut() {
sink.output_event(OutputEvent::Diagnostic {
level: "warning".to_string(),
message: format!("subdir instruction discovery: {message}"),
})?;
}
}
for instruction in discovered {
let path = instruction.path.clone();
let bytes = instruction.bytes;
let rendered = format!(
"<Additional-Context-Files>\n<Context File={}>\n{}\n</Context>\n</Additional-Context-Files>",
path.display(),
instruction.content
);
let provider_item = ProviderConversationItem::Message(ChatMessage::user(rendered));
record_subdir_provider_context_item(
session_persistence,
output_sink,
&provider_item,
&path,
)?;
record_subdir_instruction_load(
session_persistence,
output_sink,
&path,
bytes,
activity_sender,
parent_activity_id,
tool_call_id,
)?;
claimed.insert(path);
subdir_context_items.push(provider_item);
}
}
Ok(subdir_context_items)
}