Skip to main content

aimux_core/
result.rs

1//! Result types for `do_generate` and `do_stream`.
2
3use std::collections::HashMap;
4
5use futures::Stream;
6use serde::{Deserialize, Serialize};
7use std::pin::Pin;
8use ts_rs::TS;
9
10use crate::error::AiMuxError;
11use crate::shared::FileData;
12use crate::stream_part::StreamPart;
13use crate::types::{FinishReason, ProviderMetadata, ResponseMetadata, Usage, Warning};
14
15use serde_json::Value;
16
17/// A content item in the generation result.
18#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
19#[ts(export)]
20pub enum GenerateContent {
21    /// Generated text.
22    Text {
23        text: String,
24        #[serde(default, skip_serializing_if = "Option::is_none")]
25        provider_metadata: Option<ProviderMetadata>,
26    },
27    /// A tool call requested by the model.
28    ToolCall {
29        tool_call_id: String,
30        tool_name: String,
31        input: serde_json::Value,
32        /// Whether the tool call will be executed by the provider.
33        /// If false/unset, the tool call is executed by the client.
34        #[serde(default, skip_serializing_if = "Option::is_none")]
35        provider_executed: Option<bool>,
36        /// Whether the tool is dynamic (defined at runtime, e.g. MCP tools).
37        #[serde(default, skip_serializing_if = "Option::is_none")]
38        dynamic: Option<bool>,
39        /// Provider-assigned thought signature (e.g. Google Gemini
40        /// `thoughtSignature`). Must be echoed back verbatim on the follow-up
41        /// turn when the tool result is sent.
42        #[serde(default, skip_serializing_if = "Option::is_none")]
43        thought_signature: Option<String>,
44        /// Additional provider-specific metadata for the tool call.
45        #[serde(default, skip_serializing_if = "Option::is_none")]
46        provider_metadata: Option<ProviderMetadata>,
47    },
48    /// A source / citation (e.g. URL citation from search-preview models).
49    Source {
50        id: String,
51        source_type: String,
52        url: Option<String>,
53        title: Option<String>,
54        #[serde(default, skip_serializing_if = "Option::is_none")]
55        provider_metadata: Option<ProviderMetadata>,
56    },
57    /// A reasoning / thinking segment produced by the model.
58    ///
59    /// Mirrors the Vercel AI SDK `reasoning` content type. `provider_metadata`
60    /// carries provider-specific data such as the Anthropic thinking-block
61    /// signature (`providerMetadata.anthropic.signature`).
62    Reasoning {
63        text: String,
64        provider_metadata: Option<ProviderMetadata>,
65    },
66    /// A file generated by the model (e.g. an image or document).
67    ///
68    /// Mirrors the Vercel AI SDK `file` content type. `data` is a tagged union
69    /// covering base64 data, URL, or provider reference.
70    File {
71        data: FileData,
72        media_type: String,
73        #[serde(default, skip_serializing_if = "Option::is_none")]
74        provider_metadata: Option<ProviderMetadata>,
75    },
76    /// A tool result from a provider-executed tool (e.g. xAI file_search,
77    /// web_search). Emitted alongside the preceding `ToolCall` when the
78    /// provider executes the tool server-side.
79    ToolResult {
80        tool_call_id: String,
81        tool_name: String,
82        result: Value,
83        /// Whether the result is an error or error message.
84        #[serde(default, skip_serializing_if = "Option::is_none")]
85        is_error: Option<bool>,
86        /// Whether the result is preliminary (replaces prior, e.g. image previews).
87        #[serde(default, skip_serializing_if = "Option::is_none")]
88        preliminary: Option<bool>,
89        /// Whether the tool is dynamic (defined at runtime, e.g. MCP tools).
90        #[serde(default, skip_serializing_if = "Option::is_none")]
91        dynamic: Option<bool>,
92        /// Additional provider-specific metadata for the tool result.
93        #[serde(default, skip_serializing_if = "Option::is_none")]
94        provider_metadata: Option<ProviderMetadata>,
95    },
96}
97
98/// Result of `LanguageModel::do_generate` (non-streaming).
99#[derive(Debug, Serialize, Deserialize, TS)]
100#[ts(export)]
101pub struct GenerateResult {
102    /// Generated content items (text, tool calls, etc.).
103    pub content: Vec<GenerateContent>,
104    /// Why generation stopped.
105    pub finish_reason: FinishReason,
106    /// Token usage.
107    pub usage: Usage,
108    /// Warnings issued by the provider.
109    pub warnings: Vec<Warning>,
110    /// Provider-specific metadata.
111    pub provider_metadata: Option<ProviderMetadata>,
112    /// Response metadata (id, timestamp, model_id).
113    pub response: ResponseMetadata,
114    /// The request body that was sent (for debugging).
115    pub request_body: Option<serde_json::Value>,
116    /// Response headers.
117    pub response_headers: Option<HashMap<String, String>>,
118}
119
120/// Result of `LanguageModel::do_stream` (streaming).
121pub struct StreamResult {
122    /// The stream of `StreamPart` items.
123    pub stream: Pin<Box<dyn Stream<Item = Result<StreamPart, AiMuxError>> + Send>>,
124    /// The request body that was sent (for debugging).
125    pub request_body: Option<serde_json::Value>,
126    /// Response headers.
127    pub response_headers: Option<HashMap<String, String>>,
128}
129
130impl std::fmt::Debug for StreamResult {
131    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132        f.debug_struct("StreamResult")
133            .field("request_body", &self.request_body)
134            .field("response_headers", &self.response_headers)
135            .field("stream", &"<stream>")
136            .finish()
137    }
138}