magi-code 0.63.0

Repository-aware CLI coding agent for terminal work
Documentation
use crate::providers::{
    ChatMessage, ProviderConversationItem, ProviderRequest, anthropic_messages_body,
    claude_code_messages_body, codex_responses_body, openai_compatible_chat_completions_body,
};
use anyhow::Context;
use serde::Serialize;
use serde_json::Value;
use std::str::FromStr;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum PromptDumpProviderTarget {
    Anthropic,
    OpenAi,
    OpenAiCompatible,
    OpenAiCodex,
    ClaudeCode,
}

impl FromStr for PromptDumpProviderTarget {
    type Err = anyhow::Error;

    fn from_str(value: &str) -> Result<Self, Self::Err> {
        match value {
            "anthropic" => Ok(Self::Anthropic),
            "openai" => Ok(Self::OpenAi),
            "openai-compatible" => Ok(Self::OpenAiCompatible),
            "openai-codex" => Ok(Self::OpenAiCodex),
            "claude-code" => Ok(Self::ClaudeCode),
            other => anyhow::bail!(
                "unsupported --provider target '{other}' for --dump-prompt; expected one of: anthropic, openai, openai-compatible, openai-codex, claude-code"
            ),
        }
    }
}

#[derive(Debug, Serialize)]
pub(crate) struct PromptDumpFixture {
    pub(crate) system_prompt: String,
    pub(crate) tools: Value,
}

pub(crate) fn provider_prompt_dump(
    target: PromptDumpProviderTarget,
    system_prompt: String,
) -> anyhow::Result<PromptDumpFixture> {
    let request = ProviderRequest::from_conversation(
        dump_model_for_target(target),
        vec![
            ProviderConversationItem::Message(ChatMessage::system(system_prompt)),
            ProviderConversationItem::Message(ChatMessage::user(" ")),
        ],
    );
    match target {
        PromptDumpProviderTarget::Anthropic => dump_from_anthropic_body(
            anthropic_messages_body(&request.model, &request, None),
            "system",
        ),
        PromptDumpProviderTarget::ClaudeCode => dump_from_anthropic_body(
            claude_code_messages_body(&request.model, &request, false, None, None),
            "system",
        ),
        PromptDumpProviderTarget::OpenAi | PromptDumpProviderTarget::OpenAiCompatible => {
            dump_from_chat_completions_body(openai_compatible_chat_completions_body(
                &request.model,
                &request,
            ))
        }
        PromptDumpProviderTarget::OpenAiCodex => {
            dump_from_responses_body(codex_responses_body(&request.model, &request))
        }
    }
}

#[cfg(test)]
pub(crate) fn provider_prompt_dump_with_openai_compatible_responses(
    system_prompt: String,
) -> anyhow::Result<PromptDumpFixture> {
    let request = ProviderRequest::from_conversation(
        dump_model_for_target(PromptDumpProviderTarget::OpenAiCompatible),
        vec![
            ProviderConversationItem::Message(ChatMessage::system(system_prompt)),
            ProviderConversationItem::Message(ChatMessage::user(" ")),
        ],
    );
    dump_from_responses_body(crate::providers::openai_compatible_responses_body(
        &request.model,
        &request,
    ))
}

fn dump_model_for_target(target: PromptDumpProviderTarget) -> &'static str {
    match target {
        PromptDumpProviderTarget::Anthropic => crate::providers::DEFAULT_ANTHROPIC_MODEL,
        PromptDumpProviderTarget::ClaudeCode => crate::providers::DEFAULT_CLAUDE_CODE_MODEL,
        PromptDumpProviderTarget::OpenAi
        | PromptDumpProviderTarget::OpenAiCompatible
        | PromptDumpProviderTarget::OpenAiCodex => crate::providers::DEFAULT_CODEX_MODEL,
    }
}

fn dump_from_anthropic_body(body: Value, system_key: &str) -> anyhow::Result<PromptDumpFixture> {
    Ok(PromptDumpFixture {
        system_prompt: body
            .get(system_key)
            .and_then(Value::as_str)
            .context("provider body missing system prompt")?
            .to_string(),
        tools: body
            .get("tools")
            .cloned()
            .context("provider body missing tools")?,
    })
}

fn dump_from_chat_completions_body(body: Value) -> anyhow::Result<PromptDumpFixture> {
    let system_prompt = body
        .get("messages")
        .and_then(Value::as_array)
        .and_then(|messages| {
            messages.iter().find_map(|message| {
                (message.get("role").and_then(Value::as_str) == Some("system"))
                    .then(|| message.get("content").and_then(Value::as_str))
                    .flatten()
            })
        })
        .context("provider body missing system message")?
        .to_string();
    Ok(PromptDumpFixture {
        system_prompt,
        tools: body
            .get("tools")
            .cloned()
            .context("provider body missing tools")?,
    })
}

fn dump_from_responses_body(body: Value) -> anyhow::Result<PromptDumpFixture> {
    Ok(PromptDumpFixture {
        system_prompt: body
            .get("instructions")
            .and_then(Value::as_str)
            .context("provider body missing instructions")?
            .to_string(),
        tools: body
            .get("tools")
            .cloned()
            .context("provider body missing tools")?,
    })
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;

    fn request_with_system_prompt(system_prompt: &str) -> ProviderRequest {
        ProviderRequest::from_conversation(
            crate::providers::DEFAULT_CODEX_MODEL,
            vec![
                ProviderConversationItem::Message(ChatMessage::system(system_prompt)),
                ProviderConversationItem::Message(ChatMessage::user(" ")),
            ],
        )
    }

    #[test]
    fn dump_prompt_provider_payload_parity_anthropic() {
        let request = request_with_system_prompt("runtime prompt");
        let body = anthropic_messages_body(&request.model, &request, None);
        let dump = provider_prompt_dump(
            PromptDumpProviderTarget::Anthropic,
            "runtime prompt".to_string(),
        )
        .unwrap();

        assert_eq!(dump.system_prompt, body["system"]);
        assert_eq!(dump.tools, body["tools"]);
    }

    #[test]
    fn dump_prompt_provider_payload_parity_openai_family() {
        let request = request_with_system_prompt("runtime prompt");
        let body = openai_compatible_chat_completions_body(&request.model, &request);
        let dump = provider_prompt_dump(PromptDumpProviderTarget::OpenAi, "runtime prompt".into())
            .unwrap();

        assert_eq!(dump.system_prompt, body["messages"][0]["content"]);
        assert_eq!(dump.tools, body["tools"]);
    }

    #[test]
    fn dump_uses_anthropic_body_system_and_tools_shape() {
        let dump = provider_prompt_dump(
            PromptDumpProviderTarget::Anthropic,
            "runtime prompt".to_string(),
        )
        .unwrap();
        assert_eq!(dump.system_prompt, "runtime prompt");
        let first = dump.tools.as_array().unwrap().first().unwrap();
        assert_eq!(
            first
                .as_object()
                .unwrap()
                .keys()
                .cloned()
                .collect::<Vec<_>>(),
            vec!["description", "input_schema", "name"]
        );
        assert!(first.get("input_schema").is_some());
        assert!(first.get("parameters").is_none());
    }

    #[test]
    fn dump_uses_openai_chat_completions_wrapper_shape() {
        let dump =
            provider_prompt_dump(PromptDumpProviderTarget::OpenAi, "runtime".to_string()).unwrap();
        let first = dump.tools.as_array().unwrap().first().unwrap();
        assert_eq!(first["type"], json!("function"));
        assert!(first["function"].get("name").is_some());
        assert!(first["function"].get("description").is_some());
        assert!(first["function"].get("parameters").is_some());
    }

    #[test]
    fn dump_uses_codex_responses_direct_tool_shape() {
        let dump =
            provider_prompt_dump(PromptDumpProviderTarget::OpenAiCodex, "runtime".to_string())
                .unwrap();
        let first = dump.tools.as_array().unwrap().first().unwrap();
        assert_eq!(first["type"], json!("function"));
        assert!(first.get("name").is_some());
        assert!(first.get("parameters").is_some());
        assert!(first.get("function").is_none());
    }

    #[test]
    fn openai_compatible_responses_parity_helper_uses_direct_tool_shape() {
        let dump =
            provider_prompt_dump_with_openai_compatible_responses("runtime".to_string()).unwrap();
        let first = dump.tools.as_array().unwrap().first().unwrap();
        assert_eq!(first["type"], json!("function"));
        assert!(first.get("name").is_some());
        assert!(first.get("function").is_none());
    }
}