Skip to main content

harn_vm/llm/capabilities/
model.rs

1//! Capability DTOs and the wire-dialect model.
2//!
3//! Pure data types: the on-disk [`CapabilitiesFile`] schema, per-provider
4//! [`ProviderDefaults`], the resolved [`Capabilities`] struct callers consume,
5//! and the [`WireDialect`] enum that types a route's message wire format. The
6//! `ProviderRule` matrix row and the resolution engine that turns these DTOs
7//! into a `Capabilities` live in `super::rule`.
8
9use std::collections::BTreeMap;
10
11use serde::Deserialize;
12
13use super::rule::ProviderRule;
14
15/// Parsed on-disk capabilities schema. Public so harn-cli can
16/// construct one directly when wiring harn.toml overrides.
17#[derive(Debug, Clone, Deserialize, Default)]
18pub struct CapabilitiesFile {
19    /// Per-provider ordered rule lists. The first matching rule wins; a
20    /// matching rule with `extends = true` contributes only the fields it
21    /// sets and lets resolution continue to later matching rules (see
22    /// [`ProviderRule::extends`]).
23    #[serde(default)]
24    pub provider: BTreeMap<String, Vec<ProviderRule>>,
25    /// Per-provider defaults applied to every matching row and to
26    /// provider/model pairs that have no model-specific row. This keeps
27    /// transport-shape facts in data without repeating them on every
28    /// generation-specific capability row.
29    #[serde(default)]
30    pub provider_defaults: BTreeMap<String, ProviderDefaults>,
31    /// Sibling → canonical family mapping. Providers with no rule of
32    /// their own fall through to the named family (recursively).
33    #[serde(default)]
34    pub provider_family: BTreeMap<String, String>,
35    /// Per-provider adaptive rate/concurrency governor limits, keyed by
36    /// provider id. Consumed by `crate::llm::rate_governor` when the
37    /// `llm.rate_governor` flag is enabled, so provider limits stay catalog
38    /// data instead of call-site branches.
39    #[serde(default)]
40    pub provider_limits: BTreeMap<String, ProviderLimits>,
41}
42
43/// Adaptive-governor limits for one provider. Every field is optional so a
44/// catalog fragment can pin just the axes it knows; unset axes fall back to the
45/// governor's conservative built-in defaults.
46#[derive(Debug, Clone, Deserialize, Default, PartialEq)]
47pub struct ProviderLimits {
48    /// Ceiling the AIMD concurrency limiter additively climbs toward on
49    /// sustained success.
50    #[serde(default)]
51    pub max_concurrency: Option<u32>,
52    /// Floor the AIMD limiter multiplicatively decreases toward on a throttle
53    /// signal.
54    #[serde(default)]
55    pub min_concurrency: Option<u32>,
56    /// Requests-per-minute token bucket. `None` disables the RPM bucket.
57    #[serde(default)]
58    pub rpm: Option<u32>,
59    /// Tokens-per-minute token bucket, charged by estimated input + output
60    /// tokens. `None` disables the TPM bucket.
61    #[serde(default)]
62    pub tpm: Option<u64>,
63    /// Whether the AIMD adaptive concurrency loop is active. When `false`, the
64    /// concurrency limit is pinned at `max_concurrency`.
65    #[serde(default)]
66    pub adaptive: Option<bool>,
67    /// Circuit-breaker / backoff parameters. Absent means built-in defaults.
68    #[serde(default)]
69    pub backoff: Option<GovernorBackoff>,
70}
71
72/// Exponential-backoff-with-jitter parameters for the governor circuit breaker.
73/// Provider `Retry-After` values always take precedence over the computed
74/// window.
75#[derive(Debug, Clone, Deserialize, PartialEq)]
76pub struct GovernorBackoff {
77    /// First OPEN window, in milliseconds.
78    #[serde(default)]
79    pub base_ms: Option<u64>,
80    /// Ceiling for the OPEN window, in milliseconds.
81    #[serde(default)]
82    pub max_ms: Option<u64>,
83    /// Growth factor applied per consecutive OPEN cycle.
84    #[serde(default)]
85    pub multiplier: Option<f64>,
86    /// Full-jitter toggle.
87    #[serde(default)]
88    pub jitter: Option<bool>,
89}
90
91/// Provider-wide default fields merged into matching rules.
92#[derive(Debug, Clone, Deserialize, Default)]
93pub struct ProviderDefaults {
94    /// Message/request/response wire format used by shared helpers.
95    /// Known values are `openai`, `anthropic`, `gemini`, and `ollama`.
96    #[serde(default)]
97    pub message_wire_format: Option<String>,
98    /// Native tool definition wire shape. Known values are `openai`
99    /// and `anthropic`.
100    #[serde(default)]
101    pub native_tool_wire_format: Option<String>,
102    /// Whether image content blocks may reference remote URLs.
103    #[serde(default)]
104    pub image_url_input_supported: Option<bool>,
105    /// File-upload transport used by `std/files.upload`. Known values
106    /// are `anthropic` and `gemini`.
107    #[serde(default)]
108    pub file_upload_wire_format: Option<String>,
109    /// Provider-specific reasoning request shape for OpenAI-compatible
110    /// transports. Known values are `openrouter` and `enabled`.
111    #[serde(default)]
112    pub reasoning_wire_format: Option<String>,
113    #[serde(default)]
114    pub files_api_supported: Option<bool>,
115    #[serde(default)]
116    pub batch_api: Option<bool>,
117    #[serde(default)]
118    pub batch_wire_format: Option<String>,
119    #[serde(default)]
120    pub batch_input_mode: Option<String>,
121    #[serde(default)]
122    pub batch_discount_percent: Option<u32>,
123    #[serde(default)]
124    pub batch_turnaround_hours: Option<u32>,
125    #[serde(default)]
126    pub batch_max_requests: Option<u64>,
127    #[serde(default)]
128    pub batch_max_input_bytes: Option<u64>,
129    #[serde(default)]
130    pub batch_result_retention_days: Option<u32>,
131    #[serde(default)]
132    pub batch_result_ordering: Option<String>,
133    #[serde(default)]
134    pub batch_partial_failure: Option<String>,
135    #[serde(default)]
136    pub batch_cancellation: Option<String>,
137    #[serde(default)]
138    pub batch_security_notes: Option<Vec<String>>,
139    #[serde(default)]
140    pub batch_operational_notes: Option<Vec<String>>,
141    /// Explicit prompt-cache TTL values this provider can honor on request.
142    /// Empty means the route may cache, but Harn has no explicit TTL knob for
143    /// it. Known values today: `5m`, `1h`.
144    #[serde(default)]
145    pub prompt_cache_ttls: Option<Vec<String>>,
146    #[serde(default)]
147    pub seed_supported: Option<bool>,
148    #[serde(default)]
149    pub top_k_supported: Option<bool>,
150    #[serde(default)]
151    pub temperature_supported: Option<bool>,
152    #[serde(default)]
153    pub top_p_supported: Option<bool>,
154    #[serde(default)]
155    pub frequency_penalty_supported: Option<bool>,
156    #[serde(default)]
157    pub presence_penalty_supported: Option<bool>,
158    #[serde(default)]
159    pub stop_supported: Option<bool>,
160}
161
162/// Copies `src` into `dst` when `src` is set (last-writer-wins overlay).
163pub(super) fn overlay_opt<T: Clone>(dst: &mut Option<T>, src: &Option<T>) {
164    if src.is_some() {
165        dst.clone_from(src);
166    }
167}
168
169/// Copies `src` into `dst` only when `dst` is still unset (fill-the-gaps).
170pub(super) fn fill_opt<T: Clone>(dst: &mut Option<T>, src: &Option<T>) {
171    if dst.is_none() {
172        dst.clone_from(src);
173    }
174}
175
176/// Visits every `ProviderDefaults` field once, applying `$op` (`overlay_opt`
177/// or `fill_opt`) to each `(dst, src)` pair. The field roster lives here only;
178/// `overlay`/`fill_missing_from` differ solely in the merge rule they pass.
179macro_rules! merge_provider_defaults {
180    ($dst:expr, $src:expr, $op:path) => {{
181        $op(&mut $dst.message_wire_format, &$src.message_wire_format);
182        $op(
183            &mut $dst.native_tool_wire_format,
184            &$src.native_tool_wire_format,
185        );
186        $op(
187            &mut $dst.image_url_input_supported,
188            &$src.image_url_input_supported,
189        );
190        $op(
191            &mut $dst.file_upload_wire_format,
192            &$src.file_upload_wire_format,
193        );
194        $op(&mut $dst.reasoning_wire_format, &$src.reasoning_wire_format);
195        $op(&mut $dst.files_api_supported, &$src.files_api_supported);
196        $op(&mut $dst.batch_api, &$src.batch_api);
197        $op(&mut $dst.batch_wire_format, &$src.batch_wire_format);
198        $op(&mut $dst.batch_input_mode, &$src.batch_input_mode);
199        $op(
200            &mut $dst.batch_discount_percent,
201            &$src.batch_discount_percent,
202        );
203        $op(
204            &mut $dst.batch_turnaround_hours,
205            &$src.batch_turnaround_hours,
206        );
207        $op(&mut $dst.batch_max_requests, &$src.batch_max_requests);
208        $op(&mut $dst.batch_max_input_bytes, &$src.batch_max_input_bytes);
209        $op(
210            &mut $dst.batch_result_retention_days,
211            &$src.batch_result_retention_days,
212        );
213        $op(&mut $dst.batch_result_ordering, &$src.batch_result_ordering);
214        $op(&mut $dst.batch_partial_failure, &$src.batch_partial_failure);
215        $op(&mut $dst.batch_cancellation, &$src.batch_cancellation);
216        $op(&mut $dst.batch_security_notes, &$src.batch_security_notes);
217        $op(
218            &mut $dst.batch_operational_notes,
219            &$src.batch_operational_notes,
220        );
221        $op(&mut $dst.prompt_cache_ttls, &$src.prompt_cache_ttls);
222        $op(&mut $dst.seed_supported, &$src.seed_supported);
223        $op(&mut $dst.top_k_supported, &$src.top_k_supported);
224        $op(&mut $dst.temperature_supported, &$src.temperature_supported);
225        $op(&mut $dst.top_p_supported, &$src.top_p_supported);
226        $op(
227            &mut $dst.frequency_penalty_supported,
228            &$src.frequency_penalty_supported,
229        );
230        $op(
231            &mut $dst.presence_penalty_supported,
232            &$src.presence_penalty_supported,
233        );
234        $op(&mut $dst.stop_supported, &$src.stop_supported);
235    }};
236}
237
238impl ProviderDefaults {
239    pub(super) fn overlay(&mut self, other: &ProviderDefaults) {
240        merge_provider_defaults!(self, other, overlay_opt);
241    }
242
243    pub(super) fn fill_missing_from(&mut self, other: &ProviderDefaults) {
244        merge_provider_defaults!(self, other, fill_opt);
245    }
246
247    pub(super) fn has_any_field(&self) -> bool {
248        self.message_wire_format.is_some()
249            || self.native_tool_wire_format.is_some()
250            || self.image_url_input_supported.is_some()
251            || self.file_upload_wire_format.is_some()
252            || self.reasoning_wire_format.is_some()
253            || self.files_api_supported.is_some()
254            || self.batch_api.is_some()
255            || self.batch_wire_format.is_some()
256            || self.batch_input_mode.is_some()
257            || self.batch_discount_percent.is_some()
258            || self.batch_turnaround_hours.is_some()
259            || self.batch_max_requests.is_some()
260            || self.batch_max_input_bytes.is_some()
261            || self.batch_result_retention_days.is_some()
262            || self.batch_result_ordering.is_some()
263            || self.batch_partial_failure.is_some()
264            || self.batch_cancellation.is_some()
265            || self.batch_security_notes.is_some()
266            || self.batch_operational_notes.is_some()
267            || self.prompt_cache_ttls.is_some()
268            || self.seed_supported.is_some()
269            || self.top_k_supported.is_some()
270            || self.temperature_supported.is_some()
271            || self.top_p_supported.is_some()
272            || self.frequency_penalty_supported.is_some()
273            || self.presence_penalty_supported.is_some()
274            || self.stop_supported.is_some()
275    }
276}
277
278/// The message/request/response wire dialect a route speaks.
279///
280/// This is the single typed representation of what used to be encoded two
281/// different, drift-prone ways: the stringly `Capabilities.message_wire_format`
282/// field (compared against `"anthropic"`/`"gemini"`/`"ollama"` literals at a
283/// dozen call sites) and the `(is_anthropic_style, is_ollama)` boolean pair
284/// threaded independently through the transport/response layers. A closed enum
285/// makes an unhandled or mistyped dialect a compile error and removes the
286/// boolean-blindness where two `bool`s could silently disagree.
287#[derive(Debug, Clone, Copy, PartialEq, Eq)]
288pub enum WireDialect {
289    /// Anthropic native Messages API (`/v1/messages`). The only dialect that
290    /// surfaces Claude's extended-thinking stream. `message_wire_format =
291    /// "anthropic"`.
292    Anthropic,
293    /// OpenAI-compatible Chat Completions (`/v1/chat/completions`). The default
294    /// for hosted/openai-shape routes. `message_wire_format = "openai"`.
295    OpenAiCompat,
296    /// Ollama native `/api/chat`. `message_wire_format = "ollama"`.
297    Ollama,
298    /// Google Gemini `generateContent`. `message_wire_format = "gemini"`.
299    Gemini,
300}
301
302impl WireDialect {
303    /// Parse the catalog's `message_wire_format` string. Unrecognized values
304    /// (including the explicit `"openai"`) resolve to [`WireDialect::OpenAiCompat`],
305    /// exactly matching the pre-cutover behavior where every
306    /// `== "anthropic"/"gemini"/"ollama"` check fell through to the
307    /// OpenAI-compatible path.
308    pub fn from_message_wire_format(value: &str) -> WireDialect {
309        match value {
310            "anthropic" => WireDialect::Anthropic,
311            "ollama" => WireDialect::Ollama,
312            "gemini" => WireDialect::Gemini,
313            _ => WireDialect::OpenAiCompat,
314        }
315    }
316
317    /// The canonical `message_wire_format` string for display and round-trip.
318    pub fn as_str(self) -> &'static str {
319        match self {
320            WireDialect::Anthropic => "anthropic",
321            WireDialect::OpenAiCompat => "openai",
322            WireDialect::Ollama => "ollama",
323            WireDialect::Gemini => "gemini",
324        }
325    }
326
327    /// Whether this route speaks Anthropic's native Messages shape.
328    pub fn is_anthropic(self) -> bool {
329        matches!(self, WireDialect::Anthropic)
330    }
331
332    /// Whether this route speaks Ollama's native `/api/chat` shape.
333    pub fn is_ollama(self) -> bool {
334        matches!(self, WireDialect::Ollama)
335    }
336
337    /// Whether this route speaks Google Gemini's `generateContent` shape.
338    pub fn is_gemini(self) -> bool {
339        matches!(self, WireDialect::Gemini)
340    }
341}
342
343/// How the neutral `computer` tool projects onto a route's native computer-use
344/// surface (the `computer_use_style` capability). A typed enum rather than a raw
345/// string so an unknown value in a capability source is a load-time
346/// deserialize error instead of a silently-disabled computer tool.
347#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
348#[serde(rename_all = "snake_case")]
349pub enum ComputerUseStyle {
350    /// Anthropic `computer_20251124` native tool.
351    NativeAnthropic,
352    /// OpenAI Responses `computer` native tool.
353    NativeOpenai,
354    /// Accessibility / set-of-marks grounding over the universal function tool.
355    Grounded,
356    /// The plain function-schema `computer` tool (the universal default).
357    Function,
358}
359
360/// Screenshot downscaling policy applied before an image reaches the model (the
361/// `screenshot_scaling` capability). Typed for the same reason as
362/// [`ComputerUseStyle`] — an unknown value fails the capability load loudly.
363#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
364#[serde(rename_all = "snake_case")]
365pub enum ScreenshotScaling {
366    /// Fit within Anthropic's XGA (1024x768), preserving aspect ratio.
367    Xga,
368    /// Send the capture at its native resolution (OpenAI et al.).
369    Original,
370}
371
372/// Resolved capabilities for a `(provider, model)` pair. Unset rule
373/// fields resolve to `false` / empty / `None` so callers never have to
374/// unwrap an `Option<bool>` for what are really boolean gates.
375#[derive(Debug, Clone, PartialEq, Eq)]
376pub struct Capabilities {
377    pub native_tools: bool,
378    pub message_wire_format: WireDialect,
379    pub native_tool_wire_format: String,
380    pub defer_loading: bool,
381    pub tool_search: Vec<String>,
382    pub responses_api: bool,
383    pub hosted_tools: Vec<String>,
384    pub remote_mcp: bool,
385    pub conversation_state: bool,
386    pub compaction: bool,
387    pub background_mode: bool,
388    pub batch_api: bool,
389    pub batch_wire_format: Option<String>,
390    pub batch_input_mode: Option<String>,
391    pub batch_discount_percent: Option<u32>,
392    pub batch_turnaround_hours: Option<u32>,
393    pub batch_max_requests: Option<u64>,
394    pub batch_max_input_bytes: Option<u64>,
395    pub batch_result_retention_days: Option<u32>,
396    pub batch_result_ordering: Option<String>,
397    pub batch_partial_failure: Option<String>,
398    pub batch_cancellation: Option<String>,
399    pub batch_security_notes: Vec<String>,
400    pub batch_operational_notes: Vec<String>,
401    pub tool_approval_policy: Option<String>,
402    pub max_tools: Option<u32>,
403    pub prompt_caching: bool,
404    pub prompt_cache_ttls: Vec<String>,
405    pub cache_breakpoint_style: String,
406    pub vision: bool,
407    pub audio: bool,
408    pub pdf: bool,
409    pub video: bool,
410    pub files_api_supported: bool,
411    pub file_upload_wire_format: Option<String>,
412    pub structured_output: Option<String>,
413    /// Legacy mirror for CLI display and older callers.
414    pub json_schema: Option<String>,
415    pub prefers_xml_scaffolding: bool,
416    /// See [`ProviderRule::reserved_tool_call_token`].
417    pub reserved_tool_call_token: bool,
418    pub prefers_markdown_scaffolding: bool,
419    pub structured_output_mode: String,
420    pub supports_assistant_prefill: bool,
421    pub prefers_role_developer: bool,
422    pub prefers_xml_tools: bool,
423    pub thinking_block_style: String,
424    /// Whether this route emits its reasoning INLINE in the text channel as
425    /// `<think>...</think>` blocks (local Ollama/llama.cpp reasoning models,
426    /// Qwen3 via vLLM, Kimi) rather than in a separate provider reasoning
427    /// field. When true, the `llm_call` envelope builder splits those blocks
428    /// out of `text`/`prose`/`visible_text` and folds them into the reasoning
429    /// channel, mirroring how hosted providers surface a dedicated thinking
430    /// field. Derived from `thinking_block_style == "inline"` — the same
431    /// population that represents reasoning as inline `<think>` in prompt
432    /// context is the one that emits it that way in responses.
433    pub emits_inline_reasoning: bool,
434    pub thinking_modes: Vec<String>,
435    pub interleaved_thinking_supported: bool,
436    pub anthropic_beta_features: Vec<String>,
437    pub vision_supported: bool,
438    pub image_url_input_supported: bool,
439    pub preserve_thinking: bool,
440    pub server_parser: String,
441    pub honors_chat_template_kwargs: bool,
442    pub chat_template_options_field: Option<String>,
443    pub requires_completion_tokens: bool,
444    /// True when the route is served ONLY by the provider Responses API and
445    /// rejects `/v1/chat/completions` (OpenAI `*-codex` models). Harn routes
446    /// such calls through the Responses provider automatically.
447    pub chat_completions_unsupported: bool,
448    /// See [`ProviderRule::reasoning_tools_require_responses`].
449    pub reasoning_tools_require_responses: bool,
450    pub requires_streaming: bool,
451    pub reasoning_effort_supported: bool,
452    pub reasoning_effort_levels: Vec<String>,
453    pub reasoning_none_supported: bool,
454    /// See [`ProviderRule::max_thinking_budget`]. `None` means the model uses
455    /// the provider's own default ceiling.
456    pub max_thinking_budget: Option<i64>,
457    pub reasoning_disable_supported: bool,
458    /// See [`ProviderRule::reasoning_required_for_tools`].
459    pub reasoning_required_for_tools: bool,
460    pub reasoning_text_promotable: bool,
461    pub reasoning_wire_format: Option<String>,
462    pub seed_supported: bool,
463    pub top_k_supported: bool,
464    pub temperature_supported: bool,
465    pub top_p_supported: bool,
466    pub frequency_penalty_supported: bool,
467    pub presence_penalty_supported: bool,
468    pub stop_supported: bool,
469    pub allowed_tool_choice_modes: Vec<String>,
470    pub requires_tool_result_adjacency: bool,
471    pub supports_parallel_tool_calls: bool,
472    pub tools_exclude_response_format: bool,
473    pub recommended_endpoint: Option<String>,
474    pub text_tool_wire_format_supported: bool,
475    pub preferred_tool_format: Option<String>,
476    pub tool_mode_parity: Option<String>,
477    pub tool_mode_parity_notes: Option<String>,
478    pub thinking_disable_directive: Option<String>,
479    /// Per-task auto-policy reasoning-level overrides for this route.
480    /// See [`ProviderRule::auto_reasoning_overrides`].
481    pub auto_reasoning_overrides: BTreeMap<String, String>,
482    /// OpenRouter upstream provider names to exclude from routing for this
483    /// row. See [`ProviderRule::provider_route_denylist`]. Empty means "no
484    /// route restriction".
485    pub provider_route_denylist: Vec<String>,
486    /// OpenRouter upstream provider names this row is PINNED to (allowlist), in
487    /// preference order. See [`ProviderRule::openrouter_provider_order`]. Empty
488    /// means "no pin" (free OpenRouter routing).
489    pub openrouter_provider_order: Vec<String>,
490    /// Serving-quality / precision trust verdict for this route. See
491    /// [`ProviderRule::serving_precision`]. `"unverified"` when unset.
492    pub serving_precision: String,
493    /// How the neutral `computer` tool projects onto this route's native
494    /// computer-use surface. `None` means the route exposes no computer-use
495    /// surface. See [`ComputerUseStyle`].
496    pub computer_use_style: Option<ComputerUseStyle>,
497    /// Screenshot downscaling policy applied before the image reaches the
498    /// model. `None` means unset. See [`ScreenshotScaling`].
499    pub screenshot_scaling: Option<ScreenshotScaling>,
500    /// Whether this route requires echoing acknowledged safety checks on the
501    /// computer-use follow-up turn (OpenAI Responses `pending_safety_checks`
502    /// → `acknowledged_safety_checks`). See [`ProviderRule::safety_ack_flow`].
503    pub safety_ack_flow: bool,
504}
505
506impl Default for Capabilities {
507    fn default() -> Self {
508        Self {
509            native_tools: false,
510            message_wire_format: WireDialect::OpenAiCompat,
511            native_tool_wire_format: "openai".to_string(),
512            defer_loading: false,
513            tool_search: Vec::new(),
514            responses_api: false,
515            hosted_tools: Vec::new(),
516            remote_mcp: false,
517            conversation_state: false,
518            compaction: false,
519            background_mode: false,
520            batch_api: false,
521            batch_wire_format: None,
522            batch_input_mode: None,
523            batch_discount_percent: None,
524            batch_turnaround_hours: None,
525            batch_max_requests: None,
526            batch_max_input_bytes: None,
527            batch_result_retention_days: None,
528            batch_result_ordering: None,
529            batch_partial_failure: None,
530            batch_cancellation: None,
531            batch_security_notes: Vec::new(),
532            batch_operational_notes: Vec::new(),
533            tool_approval_policy: None,
534            max_tools: None,
535            prompt_caching: false,
536            prompt_cache_ttls: Vec::new(),
537            cache_breakpoint_style: "none".to_string(),
538            vision: false,
539            audio: false,
540            pdf: false,
541            video: false,
542            files_api_supported: false,
543            file_upload_wire_format: None,
544            structured_output: None,
545            json_schema: None,
546            prefers_xml_scaffolding: false,
547            reserved_tool_call_token: false,
548            prefers_markdown_scaffolding: false,
549            structured_output_mode: "none".to_string(),
550            supports_assistant_prefill: false,
551            prefers_role_developer: false,
552            prefers_xml_tools: false,
553            thinking_block_style: "none".to_string(),
554            emits_inline_reasoning: false,
555            thinking_modes: Vec::new(),
556            interleaved_thinking_supported: false,
557            anthropic_beta_features: Vec::new(),
558            vision_supported: false,
559            image_url_input_supported: true,
560            preserve_thinking: false,
561            server_parser: "none".to_string(),
562            honors_chat_template_kwargs: false,
563            chat_template_options_field: None,
564            requires_completion_tokens: false,
565            chat_completions_unsupported: false,
566            reasoning_tools_require_responses: false,
567            requires_streaming: false,
568            reasoning_effort_supported: false,
569            reasoning_effort_levels: Vec::new(),
570            reasoning_none_supported: false,
571            max_thinking_budget: None,
572            reasoning_disable_supported: true,
573            reasoning_required_for_tools: false,
574            reasoning_text_promotable: false,
575            reasoning_wire_format: None,
576            seed_supported: true,
577            top_k_supported: true,
578            temperature_supported: true,
579            top_p_supported: true,
580            frequency_penalty_supported: true,
581            presence_penalty_supported: true,
582            stop_supported: true,
583            allowed_tool_choice_modes: Vec::new(),
584            requires_tool_result_adjacency: false,
585            supports_parallel_tool_calls: true,
586            tools_exclude_response_format: false,
587            recommended_endpoint: None,
588            text_tool_wire_format_supported: true,
589            preferred_tool_format: None,
590            tool_mode_parity: None,
591            tool_mode_parity_notes: None,
592            thinking_disable_directive: None,
593            auto_reasoning_overrides: BTreeMap::new(),
594            provider_route_denylist: Vec::new(),
595            openrouter_provider_order: Vec::new(),
596            serving_precision: "unverified".to_string(),
597            computer_use_style: None,
598            screenshot_scaling: None,
599            safety_ack_flow: false,
600        }
601    }
602}