magi-code 0.96.1

Repository-aware CLI coding agent for terminal work
Documentation
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)
}