llmsdk-provider 0.1.1

Provider trait abstractions for llmsdk (Rust port of @ai-sdk/provider v4)
Documentation
//! Content parts returned from `do_generate`.
//!
//! Mirrors `language-model-v4-content.ts` plus the leaf `-text`,
//! `-reasoning`, `-file`, `-source`, `-tool-call`, `-tool-result`,
//! `-tool-approval-request`, `-custom-content`, `-reasoning-file` files.
// Rust guideline compliant 2026-02-21

use serde::{Deserialize, Serialize};

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

use super::prompt::{FilePart, TextPart};

/// One generated content unit.
///
/// Discriminated by `type` on the wire; identical tags to ai-sdk.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum Content {
    /// Plain text.
    Text(TextPart),
    /// Reasoning trace.
    Reasoning(ReasoningPart),
    /// Provider-specific opaque content.
    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>,
    },
    /// 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>,
    },
    /// File generated by the model.
    File(FilePart),
    /// Tool the provider wants to execute, pending user approval.
    ToolApprovalRequest(ToolApprovalRequest),
    /// Citation / grounding source.
    Source(Source),
    /// Tool call requested by the model.
    ToolCall(super::prompt::ToolCallPart),
    /// Tool result reported by the provider (provider-executed tools).
    ToolResult(ToolResult),
}

/// Reasoning trace produced by the model.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ReasoningPart {
    /// Reasoning text.
    pub text: String,
    /// Provider-specific options.
    #[serde(
        default,
        rename = "providerOptions",
        skip_serializing_if = "Option::is_none"
    )]
    pub provider_options: Option<ProviderOptions>,
}

/// Citation / grounding source returned by the model.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(tag = "sourceType", rename_all = "kebab-case")]
pub enum Source {
    /// Web URL source.
    Url {
        /// Source id.
        id: String,
        /// Absolute URL.
        url: String,
        /// Optional title.
        #[serde(default, skip_serializing_if = "Option::is_none")]
        title: Option<String>,
        /// Provider-specific metadata.
        #[serde(
            default,
            rename = "providerMetadata",
            skip_serializing_if = "Option::is_none"
        )]
        provider_metadata: Option<ProviderMetadata>,
    },
    /// Document source (uploaded file).
    Document {
        /// Source id.
        id: String,
        /// IANA media type.
        #[serde(rename = "mediaType")]
        media_type: String,
        /// Title.
        title: String,
        /// Optional filename.
        #[serde(default, skip_serializing_if = "Option::is_none")]
        filename: Option<String>,
        /// Provider-specific metadata.
        #[serde(
            default,
            rename = "providerMetadata",
            skip_serializing_if = "Option::is_none"
        )]
        provider_metadata: Option<ProviderMetadata>,
    },
}

/// Approval request for a provider-executed tool call.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ToolApprovalRequest {
    /// Unique approval id.
    #[serde(rename = "approvalId")]
    pub approval_id: String,
    /// Tool call awaiting approval.
    #[serde(rename = "toolCall")]
    pub tool_call: super::prompt::ToolCallPart,
    /// Provider-specific metadata.
    #[serde(
        default,
        rename = "providerMetadata",
        skip_serializing_if = "Option::is_none"
    )]
    pub provider_metadata: Option<ProviderMetadata>,
}

/// Tool result emitted by a provider-executed tool.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ToolResult {
    /// Id of the originating tool call.
    #[serde(rename = "toolCallId")]
    pub tool_call_id: String,
    /// Tool name.
    #[serde(rename = "toolName")]
    pub tool_name: String,
    /// Tool output.
    pub output: ToolResultOutput,
    /// Whether the tool result is preliminary.
    ///
    /// Preliminary tool results replace each other (e.g., image previews
    /// during `image_generation_call.partial_image` events). A non-preliminary
    /// result is always emitted before the model treats the tool call as
    /// final. Mirrors upstream `LanguageModelV4ToolResult.preliminary`.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub preliminary: Option<bool>,
    /// Provider-specific metadata.
    #[serde(
        default,
        rename = "providerMetadata",
        skip_serializing_if = "Option::is_none"
    )]
    pub provider_metadata: Option<ProviderMetadata>,
}

/// Payload variants for a tool result.
///
/// Mirrors `LanguageModelV4ToolResultOutput`. `Content` carries a list of
/// mixed text / file / custom parts.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum ToolResultOutput {
    /// Text output, sent verbatim back to the model.
    Text {
        /// Text content.
        value: String,
        /// Provider-specific options.
        #[serde(
            default,
            rename = "providerOptions",
            skip_serializing_if = "Option::is_none"
        )]
        provider_options: Option<ProviderOptions>,
    },
    /// JSON output.
    Json {
        /// JSON value.
        value: JsonValue,
        /// Provider-specific options.
        #[serde(
            default,
            rename = "providerOptions",
            skip_serializing_if = "Option::is_none"
        )]
        provider_options: Option<ProviderOptions>,
    },
    /// User denied execution.
    ExecutionDenied {
        /// Optional reason.
        #[serde(default, skip_serializing_if = "Option::is_none")]
        reason: Option<String>,
        /// Provider-specific options.
        #[serde(
            default,
            rename = "providerOptions",
            skip_serializing_if = "Option::is_none"
        )]
        provider_options: Option<ProviderOptions>,
    },
    /// Error reported as a string.
    ErrorText {
        /// Error text.
        value: String,
        /// Provider-specific options.
        #[serde(
            default,
            rename = "providerOptions",
            skip_serializing_if = "Option::is_none"
        )]
        provider_options: Option<ProviderOptions>,
    },
    /// Error reported as structured JSON.
    ErrorJson {
        /// Error payload.
        value: JsonValue,
        /// Provider-specific options.
        #[serde(
            default,
            rename = "providerOptions",
            skip_serializing_if = "Option::is_none"
        )]
        provider_options: Option<ProviderOptions>,
    },
    /// Multi-part output (text / file / custom).
    Content {
        /// Ordered output parts.
        value: Vec<ToolOutputPart>,
    },
}

/// One part inside [`ToolResultOutput::Content`].
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum ToolOutputPart {
    /// Text fragment.
    Text {
        /// Text content.
        text: String,
        /// Provider-specific options.
        #[serde(
            default,
            rename = "providerOptions",
            skip_serializing_if = "Option::is_none"
        )]
        provider_options: Option<ProviderOptions>,
    },
    /// File fragment.
    File {
        /// File payload.
        data: FileData,
        /// IANA media type.
        #[serde(rename = "mediaType")]
        media_type: String,
        /// Optional filename.
        #[serde(default, skip_serializing_if = "Option::is_none")]
        filename: Option<String>,
        /// Provider-specific options.
        #[serde(
            default,
            rename = "providerOptions",
            skip_serializing_if = "Option::is_none"
        )]
        provider_options: Option<ProviderOptions>,
    },
    /// Custom opaque fragment.
    Custom {
        /// Provider-specific options.
        #[serde(
            default,
            rename = "providerOptions",
            skip_serializing_if = "Option::is_none"
        )]
        provider_options: Option<ProviderOptions>,
    },
}