llmsdk-provider 0.1.1

Provider trait abstractions for llmsdk (Rust port of @ai-sdk/provider v4)
Documentation
//! Prompt / message types passed to a [`super::LanguageModel`].
//!
//! Mirrors `language-model-v4-prompt.ts`. JSON wire format is preserved
//! 1:1 with ai-sdk so the same provider HTTP body works across SDKs.
// Rust guideline compliant 2026-02-21

use serde::{Deserialize, Serialize};

use crate::json::JsonValue;
use crate::shared::{FileData, ProviderOptions};

/// An ordered list of [`Message`]s sent to the model.
pub type Prompt = Vec<Message>;

/// One turn in a conversation.
///
/// Tagged by `role` on the wire. Each variant carries its own content
/// shape, mirroring ai-sdk's per-role part lists.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(tag = "role", rename_all = "lowercase")]
pub enum Message {
    /// System instruction.
    System {
        /// System prompt text.
        content: String,
        /// Provider-specific options for this message.
        #[serde(
            default,
            rename = "providerOptions",
            skip_serializing_if = "Option::is_none"
        )]
        provider_options: Option<ProviderOptions>,
    },
    /// User message; multimodal.
    User {
        /// Ordered parts.
        content: Vec<UserPart>,
        /// Provider-specific options for this message.
        #[serde(
            default,
            rename = "providerOptions",
            skip_serializing_if = "Option::is_none"
        )]
        provider_options: Option<ProviderOptions>,
    },
    /// Assistant message; can include reasoning and tool calls.
    Assistant {
        /// Ordered parts.
        content: Vec<AssistantPart>,
        /// Provider-specific options for this message.
        #[serde(
            default,
            rename = "providerOptions",
            skip_serializing_if = "Option::is_none"
        )]
        provider_options: Option<ProviderOptions>,
    },
    /// Tool message carrying tool results back to the model.
    Tool {
        /// Ordered parts.
        content: Vec<ToolMessagePart>,
        /// Provider-specific options for this message.
        #[serde(
            default,
            rename = "providerOptions",
            skip_serializing_if = "Option::is_none"
        )]
        provider_options: Option<ProviderOptions>,
    },
}

/// Content part allowed in a [`Message::User`].
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum UserPart {
    /// Plain text.
    Text(TextPart),
    /// File attachment (image, audio, pdf, ...).
    File(FilePart),
}

/// Content part allowed in a [`Message::Assistant`].
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum AssistantPart {
    /// Plain text reply.
    Text(TextPart),
    /// File generated by the model.
    File(FilePart),
    /// Reasoning trace.
    Reasoning {
        /// Reasoning text.
        text: String,
        /// Provider-specific options.
        #[serde(
            default,
            rename = "providerOptions",
            skip_serializing_if = "Option::is_none"
        )]
        provider_options: Option<ProviderOptions>,
    },
    /// File generated as part of a reasoning trace.
    ReasoningFile {
        /// File payload.
        data: FileData,
        /// IANA media type.
        #[serde(rename = "mediaType")]
        media_type: String,
        /// Provider-specific options.
        #[serde(
            default,
            rename = "providerOptions",
            skip_serializing_if = "Option::is_none"
        )]
        provider_options: Option<ProviderOptions>,
    },
    /// Provider-specific opaque part. `kind` is `provider.subtype`.
    Custom {
        /// Custom kind tag, e.g. `"openai.web_search_result"`.
        kind: String,
        /// Provider-specific options.
        #[serde(
            default,
            rename = "providerOptions",
            skip_serializing_if = "Option::is_none"
        )]
        provider_options: Option<ProviderOptions>,
    },
    /// Tool invocation the model wants the client (or provider) to execute.
    ToolCall(ToolCallPart),
    /// Inline tool result (used when the same turn carries the result).
    ToolResult(ToolResultPart),
}

/// Content part allowed in a [`Message::Tool`].
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum ToolMessagePart {
    /// Result for a previously requested tool call.
    ToolResult(ToolResultPart),
    /// Approval response for a provider-executed tool call.
    ToolApprovalResponse(ToolApprovalResponsePart),
}

/// Plain text part.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct TextPart {
    /// Text content.
    pub text: String,
    /// Provider-specific options.
    #[serde(
        default,
        rename = "providerOptions",
        skip_serializing_if = "Option::is_none"
    )]
    pub provider_options: Option<ProviderOptions>,
}

/// File attachment part.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct FilePart {
    /// Optional filename hint.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub filename: Option<String>,
    /// File payload.
    pub data: FileData,
    /// IANA media type (full `type/subtype` or top-level only).
    #[serde(rename = "mediaType")]
    pub media_type: String,
    /// Provider-specific options.
    #[serde(
        default,
        rename = "providerOptions",
        skip_serializing_if = "Option::is_none"
    )]
    pub provider_options: Option<ProviderOptions>,
}

/// Tool call requested by the assistant.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ToolCallPart {
    /// Unique id for matching with [`ToolResultPart`].
    #[serde(rename = "toolCallId")]
    pub tool_call_id: String,
    /// Tool name.
    #[serde(rename = "toolName")]
    pub tool_name: String,
    /// Arguments matching the tool's input schema.
    pub input: JsonValue,
    /// `true` when the provider will execute the tool itself.
    #[serde(
        default,
        rename = "providerExecuted",
        skip_serializing_if = "Option::is_none"
    )]
    pub provider_executed: Option<bool>,
    /// `true` when the tool name is only known at runtime (e.g. MCP tools
    /// proxied through a `provider` tool). Mirrors the `dynamic` field on
    /// [`super::stream_part::StreamPart::ToolCall`].
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub dynamic: Option<bool>,
    /// Provider-specific options.
    #[serde(
        default,
        rename = "providerOptions",
        skip_serializing_if = "Option::is_none"
    )]
    pub provider_options: Option<ProviderOptions>,
}

/// Result of a tool invocation.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ToolResultPart {
    /// Id of the originating tool call.
    #[serde(rename = "toolCallId")]
    pub tool_call_id: String,
    /// Tool name.
    #[serde(rename = "toolName")]
    pub tool_name: String,
    /// Output payload.
    pub output: super::content::ToolResultOutput,
    /// Provider-specific options.
    #[serde(
        default,
        rename = "providerOptions",
        skip_serializing_if = "Option::is_none"
    )]
    pub provider_options: Option<ProviderOptions>,
}

/// User decision on a provider-executed tool call.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ToolApprovalResponsePart {
    /// Id of the originating approval request.
    #[serde(rename = "approvalId")]
    pub approval_id: String,
    /// `true` to allow execution.
    pub approved: bool,
    /// Optional human-readable reason.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub reason: Option<String>,
    /// Provider-specific options.
    #[serde(
        default,
        rename = "providerOptions",
        skip_serializing_if = "Option::is_none"
    )]
    pub provider_options: Option<ProviderOptions>,
}