Skip to main content

aimux_core/
language_model_message.rs

1//! Provider-facing message types.
2//!
3//! `LanguageModelPrompt` is the standardized prompt format that providers receive
4//! in `CallOptions.prompt`. It is converted from `ModelMessage` (user-facing)
5//! by the `generate_text` / `stream_text` functions.
6
7use crate::content::ContentPart;
8use crate::message::Role;
9use serde::{Deserialize, Serialize};
10use serde_json::Value;
11use ts_rs::TS;
12
13/// A single provider-facing message.
14///
15/// Unlike `ModelMessage` (which can have string content), this always has
16/// `content: Vec<ContentPart>` — strings are normalized to `ContentPart::Text`.
17#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, TS)]
18#[ts(export)]
19pub struct LanguageModelPromptMessage {
20    pub role: Role,
21    pub content: Vec<ContentPart>,
22    /// Message-level provider-specific options (e.g.
23    /// `anthropic.cacheControl`), applied to the last part of the message when
24    /// converting to a provider prompt. Mirrors the Vercel AI SDK
25    /// `LanguageModelV4Message.providerOptions`.
26    pub provider_options: Option<Value>,
27}
28
29/// The standardized prompt passed to `LanguageModel::do_generate` / `do_stream`.
30pub type LanguageModelPrompt = Vec<LanguageModelPromptMessage>;
31
32/// Convert user-facing `ModelMessage`s into a `LanguageModelPrompt`.
33///
34/// - String content is normalized to a single `ContentPart::Text`.
35/// - If `instructions` is provided, it is prepended as a system message.
36#[must_use]
37pub fn convert_to_language_model_prompt(
38    messages: &[crate::message::ModelMessage],
39    instructions: Option<&str>,
40) -> LanguageModelPrompt {
41    let mut result = Vec::new();
42
43    // Prepend system instructions if provided.
44    if let Some(instructions) = instructions {
45        result.push(LanguageModelPromptMessage {
46            role: Role::System,
47            content: vec![ContentPart::text(instructions)],
48            provider_options: None,
49        });
50    }
51
52    for msg in messages {
53        let parts = match &msg.content {
54            crate::message::MessageContent::Text(text) => vec![ContentPart::text(text)],
55            crate::message::MessageContent::Parts(parts) => parts.clone(),
56        };
57        result.push(LanguageModelPromptMessage {
58            role: msg.role,
59            content: parts,
60            provider_options: None,
61        });
62    }
63
64    result
65}