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::language_model_message::LanguageModelPrompt;
12use crate::shared::AbortSignal;
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 waiting for the first stream chunk (streaming only).
47    #[ts(type = "number | null")]
48    pub first_chunk_ms: Option<u64>,
49    /// Maximum idle time between consecutive stream chunks (streaming only).
50    #[ts(type = "number | null")]
51    pub chunk_ms: Option<u64>,
52}
53
54/// Options passed to `LanguageModel::do_generate` / `do_stream`.
55///
56/// This is the **provider-facing** options struct. Users interact with
57/// `GenerateTextOptions` (user-facing) which is converted to `CallOptions`
58/// by the `generate_text` / `stream_text` functions.
59#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
60#[ts(export)]
61pub struct CallOptions {
62    /// The standardized prompt (message array). Required.
63    pub prompt: LanguageModelPrompt,
64
65    /// Maximum tokens to generate.
66    pub max_output_tokens: Option<u32>,
67
68    /// Sampling temperature.
69    pub temperature: Option<f64>,
70
71    /// Stop sequences.
72    pub stop_sequences: Option<Vec<String>>,
73
74    /// Nucleus sampling `top_p`.
75    pub top_p: Option<f64>,
76
77    /// Top-k sampling.
78    pub top_k: Option<f64>,
79
80    /// Presence penalty.
81    pub presence_penalty: Option<f64>,
82
83    /// Frequency penalty.
84    pub frequency_penalty: Option<f64>,
85
86    /// Response format (text or JSON).
87    pub response_format: Option<ResponseFormat>,
88
89    /// Seed for reproducibility.
90    #[ts(type = "number | null")]
91    pub seed: Option<u64>,
92
93    /// Tools available to the model (function tools and/or provider-defined tools).
94    pub tools: Option<Vec<Tool>>,
95
96    /// How the model should choose tools.
97    pub tool_choice: ToolChoice,
98
99    /// Extra HTTP headers.
100    pub headers: Option<HashMap<String, String>>,
101
102    /// Provider-specific options (keyed by provider name).
103    pub provider_options: Option<HashMap<String, Value>>,
104
105    /// Top-level reasoning effort. Maps to OpenAI `reasoning_effort` and
106    /// Anthropic `thinking` config.
107    pub reasoning: Option<ReasoningEffort>,
108
109    /// Per-call request body overrides. Deep-merged into the provider-built
110    /// request body (after any built-in vendor override) before sending.
111    /// `null` values delete the corresponding key. See RFC-0017.
112    pub body_overrides: Option<Value>,
113
114    /// Per-call retry count override. `None` uses the provider's configured
115    /// `RetryConfig.max_retries`. `Some(0)` disables retries.
116    pub max_retries: Option<u32>,
117
118    /// Per-call timeout configuration (total / first-chunk / chunk idle).
119    /// `None` = no timeouts (provider defaults still apply at the HTTP layer).
120    pub timeout: Option<TimeoutConfiguration>,
121
122    /// Session identifier, for grouping consecutive calls into a session
123    /// (observability, see RFC-0024). Explicit values take precedence; when
124    /// `None` and the optional session inferer is enabled, one may be inferred.
125    /// Orthogonal to RFC-0019 session-affinity headers: this field is for
126    /// local grouping, while session headers in `headers` are for upstream
127    /// routing — both may share an id value but travel different paths.
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub session_id: Option<String>,
130
131    /// Abort signal for cancelling the call.
132    ///
133    /// Runtime handle — never crosses the JSON boundary (bindings that only
134    /// pass JSON cannot set it; Node bridges a JS `AbortSignal` natively).
135    #[serde(skip)]
136    #[ts(skip)]
137    pub abort_signal: Option<AbortSignal>,
138
139    /// RFC-0023 recording correlation id (internal; never serialized).
140    #[serde(skip)]
141    #[ts(skip)]
142    pub call_id: Option<String>,
143
144    /// RFC-0023 recording context (R7 快照绑定;internal;never serialized)。
145    /// 层 A 入口构造一次,provider 复制到 `HttpRequest`。None = 不录制。
146    #[serde(skip)]
147    #[ts(skip)]
148    pub recording_context: Option<crate::recording::RecordingContext>,
149
150    /// Emit raw provider stream chunks as `StreamPart::Raw` (debugging aid).
151    /// When `Some(true)`, streaming providers yield one `Raw` part per JSON
152    /// SSE event, carrying the **parsed JSON payload** of the event, emitted
153    /// before the parsed parts. Excludes the `[DONE]` sentinel; unparsable
154    /// chunks emit only `Error`. `null`/`Some(false)` = off.
155    /// Currently honored by the OpenAI-compatible family (openai / azure /
156    /// openai-compatible registry providers); other provider families ignore
157    /// it for now (RFC-0016 M2).
158    pub include_raw_chunks: Option<bool>,
159}
160
161impl CallOptions {
162    /// Create a `CallOptions` with the given prompt and all other fields set
163    /// to their defaults (`None` / `ToolChoice::Auto`).
164    ///
165    /// This is the recommended construction baseline in tests — combined with
166    /// struct-update syntax (`CallOptions { tools: Some(..), ..CallOptions::new(prompt) }`)
167    /// it avoids spelling out every `None` field, so adding a new optional
168    /// field to `CallOptions` no longer requires batch-editing every test.
169    #[must_use]
170    pub fn new(prompt: LanguageModelPrompt) -> Self {
171        CallOptions {
172            prompt,
173            max_output_tokens: None,
174            temperature: None,
175            stop_sequences: None,
176            top_p: None,
177            top_k: None,
178            presence_penalty: None,
179            frequency_penalty: None,
180            response_format: None,
181            seed: None,
182            tools: None,
183            tool_choice: ToolChoice::default(),
184            headers: None,
185            provider_options: None,
186            reasoning: None,
187            body_overrides: None,
188            max_retries: None,
189            timeout: None,
190            session_id: None,
191            abort_signal: None,
192            call_id: None,
193            recording_context: None,
194            include_raw_chunks: None,
195        }
196    }
197}