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}