systemprompt-ai 0.2.1

Provider-agnostic LLM integration for systemprompt.io AI governance — Anthropic, OpenAI, Gemini, and local models unified behind one governed pipeline with cost tracking and audit.
Documentation
use serde_json::json;

use crate::models::ai::{AiContentPart, AiMessage, MessageRole};
use crate::models::providers::anthropic::{
    AnthropicContent, AnthropicContentBlock, AnthropicImageSource, AnthropicMessage, AnthropicTool,
};
use crate::models::tools::McpTool;

pub fn convert_messages(messages: &[AiMessage]) -> (Option<String>, Vec<AnthropicMessage>) {
    let mut system_prompt = None;
    let mut anthropic_messages = Vec::new();

    for message in messages {
        match message.role {
            MessageRole::System => {
                system_prompt = Some(message.content.clone());
            },
            MessageRole::User | MessageRole::Assistant => {
                let role = match message.role {
                    MessageRole::User => "user",
                    MessageRole::Assistant => "assistant",
                    MessageRole::System => unreachable!(),
                }
                .to_string();

                let content = if message.parts.is_empty() {
                    AnthropicContent::Text(message.content.clone())
                } else {
                    AnthropicContent::Blocks(convert_to_blocks(message))
                };

                anthropic_messages.push(AnthropicMessage { role, content });
            },
        }
    }

    (system_prompt, anthropic_messages)
}

fn convert_to_blocks(message: &AiMessage) -> Vec<AnthropicContentBlock> {
    let mut blocks = Vec::new();

    if !message.content.is_empty() {
        blocks.push(AnthropicContentBlock::Text {
            text: message.content.clone(),
        });
    }

    for part in &message.parts {
        match part {
            AiContentPart::Text { text } => {
                blocks.push(AnthropicContentBlock::Text { text: text.clone() });
            },
            AiContentPart::Image { mime_type, data } => {
                blocks.push(AnthropicContentBlock::Image {
                    source: AnthropicImageSource::Base64 {
                        media_type: mime_type.clone(),
                        data: data.clone(),
                    },
                });
            },
            AiContentPart::Audio { .. } | AiContentPart::Video { .. } => {},
        }
    }

    blocks
}

pub fn convert_tools(tools: Vec<McpTool>) -> Vec<AnthropicTool> {
    tools
        .into_iter()
        .map(|tool| AnthropicTool {
            name: tool.name,
            description: tool.description,
            input_schema: tool.input_schema.unwrap_or(json!({
                "type": "object",
                "properties": {}
            })),
        })
        .collect()
}