Skip to main content

aimux_core/
options.rs

1//! `CallOptions` — the provider-facing options passed to `do_generate` / `do_stream`.
2//!
3//! Aligned with V4 `LanguageModelV4CallOptions`.
4
5use std::collections::HashMap;
6
7use serde::{Deserialize, Serialize};
8use serde_json::Value;
9use ts_rs::TS;
10
11use crate::AbortSignal;
12use crate::language_model_message::LanguageModelPrompt;
13pub use crate::tool::{FunctionTool, ProviderTool, Tool, ToolChoice};
14use crate::types::ReasoningEffort;
15
16/// How the model should format its response.
17#[derive(Debug, Clone, Serialize, Deserialize, TS)]
18#[ts(export)]
19pub enum ResponseFormat {
20    /// Plain text (default).
21    Text,
22    /// JSON output, optionally constrained to a schema.
23    Json {
24        schema: Option<Value>,
25        name: Option<String>,
26        description: Option<String>,
27    },
28}
29
30/// Per-call timeout configuration.
31///
32/// Aligned with V4 `LanguageModelV4CallOptions.timeout`
33/// (`TimeoutConfiguration`). All values are milliseconds; `None` disables the
34/// corresponding limit. A `total` timeout also covers retry backoff and the
35/// whole streamed response.
36#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, TS)]
37#[ts(export)]
38pub struct TimeoutConfiguration {
39    /// Overall timeout for the entire call (including retries and, for
40    /// streaming, the whole stream), in milliseconds.
41    // `number`, not the `bigint` ts-rs infers from u64: the JS bindings pass
42    // options through `JSON.stringify`, which throws on BigInt. Milliseconds
43    // cannot reach 2^53 (~285k years), so precision is never at stake.
44    #[ts(type = "number | null")]
45    pub total_ms: Option<u64>,
46    /// Timeout for one generation step, including that step's attempts and
47    /// retry backoff, in milliseconds. Aimux currently has one step.
48    #[ts(type = "number | null")]
49    pub step_ms: Option<u64>,
50    /// Timeout waiting for the first stream chunk (streaming only).
51    ///
52    /// Counted from operation start, so it also bounds stream establishment
53    /// and any retries before the first semantic output: it is the
54    /// user-perceived time-to-first-output budget, not a per-attempt timer.
55    #[ts(type = "number | null")]
56    pub first_chunk_ms: Option<u64>,
57    /// Maximum idle time between consecutive stream chunks (streaming only).
58    #[ts(type = "number | null")]
59    pub chunk_ms: Option<u64>,
60}
61
62/// Options passed to `LanguageModel::do_generate` / `do_stream`.
63///
64/// This is the **provider-facing** options struct. Users interact with
65/// `GenerateTextOptions` (user-facing) which is converted to `CallOptions`
66/// by the `generate_text` / `stream_text` functions.
67#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
68#[ts(export)]
69pub struct CallOptions {
70    /// The standardized prompt (message array). Required.
71    pub prompt: LanguageModelPrompt,
72
73    /// Maximum tokens to generate.
74    pub max_output_tokens: Option<u32>,
75
76    /// Sampling temperature.
77    pub temperature: Option<f64>,
78
79    /// Stop sequences.
80    pub stop_sequences: Option<Vec<String>>,
81
82    /// Nucleus sampling `top_p`.
83    pub top_p: Option<f64>,
84
85    /// Top-k sampling.
86    pub top_k: Option<f64>,
87
88    /// Presence penalty.
89    pub presence_penalty: Option<f64>,
90
91    /// Frequency penalty.
92    pub frequency_penalty: Option<f64>,
93
94    /// Response format (text or JSON).
95    pub response_format: Option<ResponseFormat>,
96
97    /// Seed for reproducibility.
98    #[ts(type = "number | null")]
99    pub seed: Option<u64>,
100
101    /// Tools available to the model (function tools and/or provider-defined tools).
102    pub tools: Option<Vec<Tool>>,
103
104    /// How the model should choose tools.
105    pub tool_choice: ToolChoice,
106
107    /// Extra HTTP headers.
108    pub headers: Option<HashMap<String, String>>,
109
110    /// Provider-specific options (keyed by provider name).
111    pub provider_options: Option<HashMap<String, Value>>,
112
113    /// Top-level reasoning effort. Maps to OpenAI `reasoning_effort` and
114    /// Anthropic `thinking` config.
115    pub reasoning: Option<ReasoningEffort>,
116
117    /// Per-call request body overrides. Deep-merged into the provider-built
118    /// request body (after any built-in vendor override) before sending.
119    /// `null` values delete the corresponding key. See RFC-0017.
120    pub body_overrides: Option<Value>,
121
122    /// Per-call retry count override. `None` uses the provider's configured
123    /// Core operation retry. `Some(0)` disables retries.
124    pub max_retries: Option<u32>,
125
126    /// Per-call timeout configuration (total / first-chunk / chunk idle).
127    /// `None` = no timeouts (provider defaults still apply at the HTTP layer).
128    pub timeout: Option<TimeoutConfiguration>,
129
130    /// Session identifier, for grouping consecutive calls into a session
131    /// (observability, see RFC-0024). Explicit values take precedence; when
132    /// `None` and the optional session inferer is enabled, one may be inferred.
133    /// Orthogonal to RFC-0019 session-affinity headers: this field is for
134    /// local grouping, while session headers in `headers` are for upstream
135    /// routing — both may share an id value but travel different paths.
136    #[serde(skip_serializing_if = "Option::is_none")]
137    pub session_id: Option<String>,
138
139    /// Abort signal for cancelling the call.
140    ///
141    /// Runtime handle — never crosses the JSON boundary (bindings that only
142    /// pass JSON cannot set it; Node bridges a JS `AbortSignal` natively).
143    #[serde(skip)]
144    #[ts(skip)]
145    pub abort_signal: Option<AbortSignal>,
146
147    /// RFC-0023 recording correlation id (internal; never serialized).
148    #[serde(skip)]
149    #[ts(skip)]
150    pub call_id: Option<String>,
151
152    /// RFC-0023 recording context (R7 快照绑定;internal;never serialized)。
153    /// 层 A 入口构造一次,provider 复制到 `HttpRequest`。None = 不录制。
154    #[serde(skip)]
155    #[ts(skip)]
156    pub recording_context: Option<crate::recording::RecordingContext>,
157
158    /// Emit raw provider stream chunks as `StreamPart::Raw` (debugging aid).
159    /// When `Some(true)`, streaming providers yield one `Raw` part per JSON
160    /// SSE event, carrying the **parsed JSON payload** of the event, emitted
161    /// before the parsed parts. Excludes the `[DONE]` sentinel; unparsable
162    /// chunks emit only `Error`. `null`/`Some(false)` = off.
163    /// Currently honored by the OpenAI-compatible family (openai / azure /
164    /// openai-compatible registry providers); other provider families ignore
165    /// it for now (RFC-0016 M2).
166    pub include_raw_chunks: Option<bool>,
167}
168
169impl CallOptions {
170    /// Clone these options for one composite child step (Router child, MoA
171    /// reference or aggregator): the recording context, when present, is
172    /// replaced by a child context labeled `step`, so the child's exchanges
173    /// and retry attempts are recorded as their own step instead of blending
174    /// into the parent operation's.
175    #[must_use]
176    pub fn for_step(&self, step: impl Into<String>) -> Self {
177        let mut child = self.clone();
178        child.recording_context = self
179            .recording_context
180            .as_ref()
181            .map(|context| context.child(step.into()));
182        child
183    }
184
185    /// Create a `CallOptions` with the given prompt and all other fields set
186    /// to their defaults (`None` / `ToolChoice::Auto`).
187    ///
188    /// This is the recommended construction baseline in tests — combined with
189    /// struct-update syntax (`CallOptions { tools: Some(..), ..CallOptions::new(prompt) }`)
190    /// it avoids spelling out every `None` field, so adding a new optional
191    /// field to `CallOptions` no longer requires batch-editing every test.
192    #[must_use]
193    pub fn new(prompt: LanguageModelPrompt) -> Self {
194        CallOptions {
195            prompt,
196            max_output_tokens: None,
197            temperature: None,
198            stop_sequences: None,
199            top_p: None,
200            top_k: None,
201            presence_penalty: None,
202            frequency_penalty: None,
203            response_format: None,
204            seed: None,
205            tools: None,
206            tool_choice: ToolChoice::default(),
207            headers: None,
208            provider_options: None,
209            reasoning: None,
210            body_overrides: None,
211            max_retries: None,
212            timeout: None,
213            session_id: None,
214            abort_signal: None,
215            call_id: None,
216            recording_context: None,
217            include_raw_chunks: None,
218        }
219    }
220}